Compare commits

..

3 Commits

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

View File

@@ -1,36 +1,3 @@
{ {
"presets": [ "presets": ["env", "react", "stage-0"]
["@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-do-expressions",
"@babel/plugin-proposal-function-bind",
"@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-private-methods", { "loose": true }],
["@babel/plugin-proposal-private-property-in-object", { "loose": true }]
]
} }

View File

@@ -1,7 +0,0 @@
Dockerfile
.dockerignore
.gitignore
README.md
build
node_modules

View File

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

View File

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

View File

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

14
.gitattributes vendored
View File

@@ -1,14 +0,0 @@
# Set the default behavior, in case people don't have core.autocrlf set, in order to prevent line ending inconsistency.
* text=auto
# Explicitly declare text files you want to always be normalized and converted
# to native line endings on checkout.
*.jsx text
*.js text
# Declare files that will always have CRLF line endings on checkout.
# *.sln text eol=crlf
# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary

View File

@@ -1,28 +0,0 @@
# This is a basic deployment workflow triggered by pushes to the alpha branch.
name: Auto-Deploy
# Controls when the action will run. Workflow runs when the alpha branch receives a push event
on:
workflow_dispatch:
push:
branches:
- alpha
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
downloadcode:
runs-on: self-hosted
steps:
- shell: bash
run: |
rm -Rf ./coriolis
rm -Rf ./coriolis-data
git clone https://github.com/alex-williams/coriolis.git --single-branch --branch alpha
git clone https://github.com/alex-williams/coriolis-data.git --single-branch --branch alpha
cd coriolis-data
npm install
cd ../coriolis
npm install
npm run build
sudo -u www-data cp -r ./build/* /var/www/newdisk/coriolis.brighter-applications.co.uk/

3
.gitignore vendored
View File

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

15
.travis.yml Normal file
View File

@@ -0,0 +1,15 @@
language: node_js
notifications:
email: false
sudo: false
node_js:
- "4.8.1"
cache:
directories:
- node_modules
before_script:
script:
- npm run lint
- npm test

View File

@@ -1,25 +0,0 @@
#syntax=docker/dockerfile:1.4
# Run this from within this directory. Change the location of coriolis-data repo and image name/tag as needed.
# docker buildx build --build-context data=../coriolis-data --tag coriolis .
FROM node:18-alpine
# TODO: For a production build, we may want to just build the bundle and copy that in. No need for local copy of source.
WORKDIR /app
ADD . .
COPY --from=data . /coriolis-data/
# Git is required before install if any modules (like coriolis-data) are loaded from github
RUN apk update
RUN apk add git
WORKDIR /app/coriolis-data
RUN npm install
WORKDIR /app
RUN npm install
# Bundle for production config with webpack & log
RUN npm run build > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)
# Optimally, this will start a static asset server like nginx/apache. Currently, this will start dev webpack server.
CMD ["npm", "start"]
EXPOSE 3300

View File

@@ -1,23 +0,0 @@
#syntax=docker/dockerfile:1.4
# Run this from within this directory. Change the location of coriolis-data repo and image name/tag as needed.
# docker buildx build --build-context data=../coriolis-data --tag coriolis -f ./Dockerfile-dev .
FROM node:18-alpine
WORKDIR /app
ADD . .
COPY --from=data . /coriolis-data/
# Install git & any other desired in-container dev tools
# Git is required before install if any modules (like coriolis-data) are loaded from github
RUN apk update
RUN apk add git
WORKDIR /app/coriolis-data
RUN npm install
WORKDIR /app
RUN npm install
CMD ["npm", "start"]
EXPOSE 3300

View File

@@ -1,32 +0,0 @@
#syntax=docker/dockerfile:1.4
# Run this from within this directory. Change the location of coriolis-data repo and image name/tag as needed.
# docker buildx build --build-context data=../coriolis-data --tag coriolis:0.0.7-local-prod -f Dockerfile-local-prod .
# docker run -d -p 80:8080 coriolis:0.0.7-local-prod
FROM node:18-alpine
# TODO: For a production build, we may want to just build the bundle and copy that in. No need for local copy of source.
WORKDIR /app
ADD . .
# COPY --from=data . /coriolis-data/
# Git is required before install if any modules (like coriolis-data is now referenced in the package.json) are loaded from github
RUN apk update
RUN apk add git
# WORKDIR /app/coriolis-data
# RUN npm install
# WORKDIR /app
# RUN npm install
# Bundle for production config with webpack & log
# In this version of the dockerfile, I'm deferring automated webpack build so I can monitor a manual build
# RUN npm run build > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)
RUN npm install -g http-server
# Optimally, this will start a static asset server like nginx/apache. Currently, this will start dev webpack server.
# CMD ["http-server", "/app/build", "-c-1"]
CMD ["/bin/ash"]
# CMD [""]
EXPOSE 8080

View File

@@ -1,24 +0,0 @@
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.

View File

@@ -1,4 +1,4 @@
[![Chat to us on Discord](https://img.shields.io/badge/Discord-EDCD%20%23coriolis-blue.svg?style=social)](https://discord.gg/0uwCh6R62aPRjk9w) ![Latest Release](https://img.shields.io/github/release/EDCD/coriolis.svg) [![Build Status](https://travis-ci.org/EDCD/coriolis.svg?branch=master)](https://travis-ci.org/EDCD/coriolis) [![Chat to us on Discord](https://img.shields.io/badge/Discord-EDCD%20%23coriolis-blue.svg?style=social)](https://discord.gg/0uwCh6R62aPRjk9w)
## About ## About
@@ -8,50 +8,50 @@ Coriolis was created using assets and imagery from Elite: Dangerous, with the pe
## Contributing ## Contributing
- [Submit issues](https://github.com/EDCD/coriolis/issues) Please [submit issues](https://github.com/EDCD/coriolis/issues), or better yet [pull requests](https://github.com/EDCD/coriolis/pulls) for any corrections or additions to the database or the code.
- [Submit pull requests](https://github.com/EDCD/coriolis/pulls) targetting `develop` branch
- Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)! ### Translations
Please use the OneSky translation site to suggest new translations: http://edcd-coriolis.oneskyapp.com
These will be merged regularly by the project manager.
### Feature Requests, Suggestions & Bugs
Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
## Development ## Development
This release includes the ability to run the app as a Docker container. See the [Developer's Guide](https://github.com/EDCD/coriolis/wiki/Developing-for-Coriolis) in the wiki.
```sh
> git clone https://github.com/EDCD/coriolis.git
> git clone https://github.com/EDCD/coriolis-data.git
> cd coriolis
> docker buildx build --build-context data=../coriolis-data --tag coriolis .
> docker run -d -p 3300:3300 coriolis
```
Or to run an instance of coriolis without Docker Desktop, perform the following steps in a shell:
```sh
> git clone https://github.com/EDCD/coriolis.git
> git clone https://github.com/EDCD/coriolis-data.git
> cd ./coriolis-data
> npm install
> cd ../coriolis
> npm install
> npm start
```
You will then have a development server running on `localhost:3300`.
### Ship and Module Database ### Ship and Module Database
See the [Data wiki](https://github.com/EDCD/coriolis-data/wiki) for details on structure, etc. See the [Data wiki](https://github.com/cmmcleod/coriolis-data/wiki) for details on structure, etc.
## Deployment
Follow the steps for [Development](#development) as above, but instead ## License
of `npm start` you'll want to:
```sh 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
> npm run build [terms and conditions](https://www.frontierstore.net/terms-and-conditions/).
```
this will result in a `build/` directory being created containing all the necessary files. The code (Javascript, CSS, HTML, and SVG files only) specificially for Coriolis.io is released under the MIT License.
After this you need to serve the files in some manner. Copyright (c) 2015 Coriolis.io, Colin McLeod
Either configure your webserver to make the actual `build/` directory
visible on the web, or alternatively copy it to somewhere to serve it Permission is hereby granted, free of charge, to any person obtaining a copy
from. 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.

View File

@@ -1,50 +1,50 @@
{ {
"type_6_transporter": { "type_6_transporter": {
"Cargo": "A0p0tdFal8d8s8f4-----04040303430101-.Iw18UA==.Aw18UA==.", "Cargo": "A0p0tdFal8d8s8f4-----04040303430101.Iw1/kA==.Aw1/kA==.",
"Miner": "A0p5tdFal8d8s8f42l2l---040403451q0101-.Iw18UA==.Aw18UA==.", "Miner": "A0p5tdFal8d8s8f42l2l---040403451q0101.Iw1/kA==.Aw1/kA==.",
"Hopper": "A0p0tdFal8d0s8f41717---030302024300--.Iw18UA==.Aw18UA==." "Hopper": "A0p0tdFal8d0s8f41717---030302024300-.Iw1/kA==.Aw1/kA==."
}, },
"type_7_transport": { "type_7_transport": {
"Cargo": "A0p0tiFfliddsdf5--------0505040403480101--.Iw18eQ==.Aw18eQ==.", "Cargo": "A0p0tiFfliddsdf5--------0505040403480101.Iw18aQ==.Aw18aQ==.",
"Miner": "A0pdtiFflid8sdf5--2l2l----0505041v03450000--.Iw18eQ==.Aw18eQ==." "Miner": "A0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ==."
}, },
"federal_dropship": { "federal_dropship": {
"Cargo": "A0pdtiFflnddsif4-1717------05040448--020201-.Iw18RQ==.Aw18RQ==." "Cargo": "A0pdtiFflnddsif4-1717------05040448--020201.Iw18eQ==.Aw18eQ==."
}, },
"asp": { "asp": {
"Miner": "A2pftfFflidfskf50s0s24242l2l---04054a1q02022o27-.Iw18eQ==.Aw18eQ==." "Miner": "A2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ==."
}, },
"imperial_clipper": { "imperial_clipper": {
"Cargo": "A0p5tiFflndisnf4--0s0s----0605450302020101-.Iw18WQ==.Aw18WQ==.", "Cargo": "A0p5tiFflndisnf4--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==.",
"Dream": "A2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o--.AwRj4yWU1Yg=.CwBhCYy6YRigzPIA.", "Dream": "A2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o-.AwRj4yWU1I==.CwBhCYy6YRigzLIA.",
"Current": "A0patkFflndfskf4-----------------.AwRj4yWU1Yg=.CwBhCYy6YRigzPIA." "Current": "A0patkFflndfskf4----------------.AwRj4yWU1I==.CwBhCYy6YRigzLIA."
}, },
"type_9_heavy": { "type_9_heavy": {
"Current": "A0patsFklndnsif6---------0706054a0303020224--.AwRj4yo5iA==.EwBhEYy6d6g=." "Current": "A0patsFklndnsif6---------0706054a0303020224.AwRj4yoo.EwBhEYy6dsg=."
}, },
"python": { "python": {
"Cargo": "A0patnFflidsssf5---------050505040448020201-.Iw18eAMQ.Aw18RQ==.", "Cargo": "A0patnFflidsssf5---------050505040448020201.Iw18eQ==.Aw18eQ==.",
"Miner": "A0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o-.Iw18eAMQ.IwBhBYy6dkCYRA==.", "Miner": "A0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.IwBhBYy6dkCYg===.",
"Dream": "A2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201-.Iw1+gDByUA==.EwBhEYy6e0VEA===.", "Dream": "A2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201.Iw1+gDBxA===.EwBhEYy6e0WEA===.",
"Missile": "A0pttoFjljdystf52f2g2d2ePh----04044j03---00--.Iw18eAMQ.Aw18RQ==." "Missile": "A0pttoFjljdystf52f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ==."
}, },
"anaconda": { "anaconda": {
"Dream": "A4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d04-0303326b-.AwRj4yo5dzhA.MwBhCYy6duvARhEA.", "Dream": "A4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d04-0303326b.AwRj4yo5dyg=.MwBhCYy6duvARiA=.",
"Cargo": "A0patnFklndnsxf5----------------06050505040404-45030301-.Iw18ZUAxA===.Aw18ZXEA.", "Cargo": "A0patnFklndnsxf5----------------06050505040404-45030301.Iw18ZVA=.Aw18ZVA=.",
"Current": "A0patnFklndksxf5----------------06050505040404-03034524-.Iw18ZUAxA===.Aw18ZXEA.", "Current": "A0patnFklndksxf5----------------06050505040404-03034524.Iw18ZVA=.Aw18ZVA=.",
"Explorer": "A0patnFklndksxf5--------0202------f7050505040s37--2i4524-.AwRj4yVKJ9jCA===.AwhMIyumQRgkA===.", "Explorer": "A0patnFklndksxf5--------0202------f7050505040s37-2f2i4524.AwRj4yVKJ9hA.AwhMIyumQRhEA===.",
"Test": "A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b-.Iw18ZUAxA===.Aw18ZXEA." "Test": "A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.Iw18ZVA=.Aw18ZVA=."
}, },
"diamondback_explorer": { "diamondback_explorer": {
"Explorer": "A0p0tdFfldddsdf5---0202--320p432i----.AwRj4zTZaA==.AwiMIyqo." "Explorer": "A0p0tdFfldddsdf5---0202--320p432i2f-.AwRj4zTYg===.AwiMIyoo."
}, },
"vulture": { "vulture": {
"Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a-5d27662j--.AwRj4z2Gg===.MwBhBYy6oJmAjLMQ." "Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a-5d27662j.AwRj4z2I.MwBhBYy6oJmAjLIA."
}, },
"fer_de_lance": { "fer_de_lance": {
"Attack": "A2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27--.Iw18aAMQ.CwBhrSu8EZxEA===." "Attack": "A2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.CwBhrSu8EZyA."
}, },
"eagle": { "eagle": {
"Figther": "A4p0t5F5l3d5s5f20p0p24-4053-2j---.Iw18gDJQ.Aw19kA==." "Figther": "A4p0t5F5l3d5s5f20p0p24-4053-2j-.Iw18kA==.Aw18kA==."
} }
} }

View File

@@ -1,366 +0,0 @@
[
{
"header": {
"appName": "Inara",
"appVersion": "1.0",
"appURL": "https:\/\/inara.cz\/cmdr-fleet\/123\/123\/",
"appCustomProperties": {
"inaraCommanderID": 123,
"inaraShipID": 123
}
},
"data": {
"Ship": "krait_mkii",
"ShipID": 7,
"ShipName": "pancake hammer",
"ShipIdent": "PH-01",
"HullValue": 44160710,
"ModulesValue": 111274094,
"Rebuy": 7771743,
"Modules": [
{
"Slot": "largehardpoint1",
"Item": "hpt_mininglaser_fixed_small",
"On": true
},
{
"Slot": "largehardpoint2",
"Item": "hpt_cannon_gimbal_large",
"On": true,
"Engineering": {
"BlueprintName": "weapon_overcharged",
"Level": 2,
"Quality": 1,
"ExperimentalEffect": "special_auto_loader"
}
},
{
"Slot": "largehardpoint3",
"Item": "hpt_cannon_gimbal_large",
"On": true,
"Engineering": {
"BlueprintName": "weapon_overcharged",
"Level": 2,
"Quality": 1,
"ExperimentalEffect": "special_auto_loader"
}
},
{
"Slot": "mediumhardpoint1",
"Item": "hpt_basicmissilerack_fixed_medium",
"On": true,
"Engineering": {
"BlueprintName": "weapon_highcapacity",
"Level": 5,
"Quality": 1
}
},
{
"Slot": "mediumhardpoint2",
"Item": "hpt_basicmissilerack_fixed_medium",
"On": true
},
{
"Slot": "tinyhardpoint1",
"Item": "hpt_heatsinklauncher_turret_tiny",
"On": true
},
{
"Slot": "tinyhardpoint2",
"Item": "hpt_cloudscanner_size0_class3",
"On": true
},
{
"Slot": "tinyhardpoint3",
"Item": "hpt_shieldbooster_size0_class5",
"On": true
},
{
"Slot": "tinyhardpoint4",
"Item": "hpt_shieldbooster_size0_class5",
"On": true,
"Priority": 1
},
{
"Slot": "slot01_size6",
"Item": "int_cargorack_size6_class1",
"On": true,
"Priority": 1
},
{
"Slot": "slot02_size6",
"Item": "int_cargorack_size6_class1",
"On": true,
"Priority": 1
},
{
"Slot": "slot03_size5",
"Item": "int_guardianfsdbooster_size5",
"On": true
},
{
"Slot": "slot04_size5",
"Item": "int_fighterbay_size5_class1",
"On": true
},
{
"Slot": "slot05_size4",
"Item": "int_shieldgenerator_size4_class5",
"On": true
},
{
"Slot": "slot06_size3",
"Item": "int_dronecontrol_collection_size3_class4",
"On": true
},
{
"Slot": "slot07_size3",
"Item": "int_dronecontrol_collection_size3_class4",
"On": true
},
{
"Slot": "slot08_size2",
"Item": "int_refinery_size2_class2",
"On": true
},
{
"Slot": "slot09_size1",
"Item": "int_dronecontrol_prospector_size1_class4",
"On": true
},
{
"Slot": "powerplant",
"Item": "int_powerplant_size7_class5",
"On": true,
"Priority": 1
},
{
"Slot": "mainengines",
"Item": "int_engine_size6_class5",
"On": true
},
{
"Slot": "frameshiftdrive",
"Item": "int_hyperdrive_size5_class5",
"On": true,
"Engineering": {
"BlueprintName": "fsd_longrange",
"Level": 2,
"Quality": 0.861
}
},
{
"Slot": "lifesupport",
"Item": "int_lifesupport_size4_class2",
"On": true,
"Priority": 3
},
{
"Slot": "powerdistributor",
"Item": "int_powerdistributor_size7_class5",
"On": true
},
{
"Slot": "radar",
"Item": "int_sensors_size6_class2",
"On": true
},
{
"Slot": "fueltank",
"Item": "int_fueltank_size5_class3",
"On": true,
"Priority": 1
},
{
"Slot": "armour",
"Item": "krait_mkii_armour_grade3",
"On": true,
"Priority": 1,
"Engineering": {
"BlueprintName": "armour_heavyduty",
"Level": 5,
"Quality": 1
}
}
]
}
},
{
"header": {
"appName": "Inara",
"appVersion": "1.0",
"appURL": "https:\/\/inara.cz\/cmdr-fleet\/123\/123\/",
"appCustomProperties": {
"inaraCommanderID": 123,
"inaraShipID": 123
}
},
"data": {
"Ship": "diamondbackxl",
"ShipID": 11,
"ShipName": "star Hopper",
"ShipIdent": "PH-02",
"HullValue": 1615649,
"ModulesValue": 16981039,
"Rebuy": 929837,
"Modules": [
{
"Slot": "tinyhardpoint1",
"Item": "hpt_heatsinklauncher_turret_tiny",
"On": true,
"Value": 3072
},
{
"Slot": "slot01_size4",
"Item": "int_fuelscoop_size4_class5",
"On": true,
"Priority": 3,
"Value": 2862364
},
{
"Slot": "slot02_size4",
"Item": "int_guardianfsdbooster_size4",
"On": true,
"Value": 2847499
},
{
"Slot": "slot03_size3",
"Item": "int_shieldgenerator_size3_class2",
"On": true,
"Value": 18812,
"Engineering": {
"BlueprintName": "shieldgenerator_thermic",
"Level": 3,
"Quality": 1,
"ExperimentalEffect": "special_shield_health"
}
},
{
"Slot": "slot04_size3",
"Item": "int_repairer_size3_class5",
"On": true,
"Value": 2302911
},
{
"Slot": "slot05_size2",
"Item": "int_buggybay_size2_class2",
"On": true,
"Priority": 3,
"Value": 21600
},
{
"Slot": "slot06_size2",
"Item": "int_cargorack_size2_class1",
"On": true,
"Priority": 1,
"Value": 2852
},
{
"Slot": "slot07_size1",
"Item": "int_supercruiseassist",
"On": true,
"Priority": 3,
"Value": 9121
},
{
"Slot": "slot08_size1",
"Item": "int_detailedsurfacescanner_tiny",
"On": true,
"Value": 250000,
"Engineering": {
"BlueprintName": "sensor_expanded",
"Level": 5,
"Quality": 1
}
},
{
"Slot": "powerplant",
"Item": "int_powerplant_size4_class5",
"On": true,
"Priority": 1,
"Value": 1441233,
"Engineering": {
"BlueprintName": "powerplant_boosted",
"Level": 1,
"Quality": 1
}
},
{
"Slot": "mainengines",
"Item": "int_engine_size4_class5",
"On": true,
"Value": 1610080,
"Engineering": {
"BlueprintName": "engine_dirty",
"Level": 5,
"Quality": 1,
"ExperimentalEffect": "special_engine_lightweight"
}
},
{
"Slot": "frameshiftdrive",
"Item": "int_hyperdrive_size5_class5",
"On": true,
"Value": 5103953,
"Engineering": {
"BlueprintName": "fsd_longrange",
"Level": 5,
"Quality": 1,
"ExperimentalEffect": "special_fsd_lightweight"
}
},
{
"Slot": "lifesupport",
"Item": "int_lifesupport_size3_class2",
"On": true,
"Value": 10133,
"Engineering": {
"BlueprintName": "misc_lightweight",
"Level": 3,
"Quality": 1
}
},
{
"Slot": "powerdistributor",
"Item": "int_powerdistributor_size4_class5",
"On": true,
"Value": 389022,
"Engineering": {
"BlueprintName": "powerdistributor_highfrequency",
"Level": 4,
"Quality": 1
}
},
{
"Slot": "radar",
"Item": "int_sensors_size3_class2",
"On": true,
"Value": 10133,
"Engineering": {
"BlueprintName": "sensor_lightweight",
"Level": 5,
"Quality": 1
}
},
{
"Slot": "fueltank",
"Item": "int_fueltank_size5_class3",
"On": true,
"Priority": 1,
"Value": 97754
},
{
"Slot": "armour",
"Item": "diamondbackxl_armour_grade1",
"On": true,
"Priority": 1,
"Engineering": {
"BlueprintName": "armour_heavyduty",
"Level": 5,
"Quality": 1
}
}
]
}
}
]

View File

@@ -1,8 +0,0 @@
{
"krait_mkii": {
"Imported pancake hammer": "A2pptkFflidussf52l1o1o2g2g020g040405051Ofr45C9C91oP3.Iw18eQ==.AwRgzKIkA===."
},
"diamondback_explorer": {
"Imported star Hopper": "A0pataFflddfsdf5---02---321P430iv6013w2i.Iw18SQ==.AwRm44GYpKg=."
}
}

View File

@@ -1,188 +0,0 @@
[
{
"header": {
"appName": "Inara",
"appVersion": "1.0",
"appURL": "https:\/\/inara.cz\/cmdr-fleet\/123\/123\/",
"appCustomProperties": {
"inaraCommanderID": 123,
"inaraShipID": 123
}
},
"data": {
"Ship": "krait_mkii",
"ShipID": 7,
"ShipName": "pancake hammer",
"ShipIdent": "PH-01",
"HullValue": 44160710,
"ModulesValue": 111274094,
"Rebuy": 7771743,
"Modules": [
{
"Slot": "largehardpoint1",
"Item": "hpt_mininglaser_fixed_small",
"On": true
},
{
"Slot": "largehardpoint2",
"Item": "hpt_cannon_gimbal_large",
"On": true,
"Engineering": {
"BlueprintName": "weapon_overcharged",
"Level": 2,
"Quality": 1,
"ExperimentalEffect": "special_auto_loader"
}
},
{
"Slot": "largehardpoint3",
"Item": "hpt_cannon_gimbal_large",
"On": true,
"Engineering": {
"BlueprintName": "weapon_overcharged",
"Level": 2,
"Quality": 1,
"ExperimentalEffect": "special_auto_loader"
}
},
{
"Slot": "mediumhardpoint1",
"Item": "hpt_basicmissilerack_fixed_medium",
"On": true,
"Engineering": {
"BlueprintName": "weapon_highcapacity",
"Level": 5,
"Quality": 1
}
},
{
"Slot": "mediumhardpoint2",
"Item": "hpt_basicmissilerack_fixed_medium",
"On": true
},
{
"Slot": "tinyhardpoint1",
"Item": "hpt_heatsinklauncher_turret_tiny",
"On": true
},
{
"Slot": "tinyhardpoint2",
"Item": "hpt_cloudscanner_size0_class3",
"On": true
},
{
"Slot": "tinyhardpoint3",
"Item": "hpt_shieldbooster_size0_class5",
"On": true
},
{
"Slot": "tinyhardpoint4",
"Item": "hpt_shieldbooster_size0_class5",
"On": true,
"Priority": 1
},
{
"Slot": "slot01_size6",
"Item": "int_cargorack_size6_class1",
"On": true,
"Priority": 1
},
{
"Slot": "slot02_size6",
"Item": "int_cargorack_size6_class1",
"On": true,
"Priority": 1
},
{
"Slot": "slot03_size5",
"Item": "int_guardianfsdbooster_size5",
"On": true
},
{
"Slot": "slot04_size5",
"Item": "int_fighterbay_size5_class1",
"On": true
},
{
"Slot": "slot05_size4",
"Item": "int_shieldgenerator_size4_class5",
"On": true
},
{
"Slot": "slot06_size3",
"Item": "int_dronecontrol_collection_size3_class4",
"On": true
},
{
"Slot": "slot07_size3",
"Item": "int_dronecontrol_collection_size3_class4",
"On": true
},
{
"Slot": "slot08_size2",
"Item": "int_refinery_size2_class2",
"On": true
},
{
"Slot": "slot09_size1",
"Item": "int_dronecontrol_prospector_size1_class4",
"On": true
},
{
"Slot": "powerplant",
"Item": "int_powerplant_size7_class5",
"On": true,
"Priority": 1
},
{
"Slot": "mainengines",
"Item": "int_engine_size6_class5",
"On": true
},
{
"Slot": "frameshiftdrive",
"Item": "int_hyperdrive_size5_class5",
"On": true,
"Engineering": {
"BlueprintName": "fsd_longrange",
"Level": 2,
"Quality": 0.861
}
},
{
"Slot": "lifesupport",
"Item": "int_lifesupport_size4_class2",
"On": true,
"Priority": 3
},
{
"Slot": "powerdistributor",
"Item": "int_powerdistributor_size7_class5",
"On": true
},
{
"Slot": "radar",
"Item": "int_sensors_size6_class2",
"On": true
},
{
"Slot": "fueltank",
"Item": "int_fueltank_size5_class3",
"On": true,
"Priority": 1
},
{
"Slot": "armour",
"Item": "krait_mkii_armour_grade3",
"On": true,
"Priority": 1,
"Engineering": {
"BlueprintName": "armour_heavyduty",
"Level": 5,
"Quality": 1
}
}
]
}
}
]

View File

@@ -18,13 +18,13 @@ describe('Import Modal', function() {
const mockContext = { const mockContext = {
language: getLanguage('en'), language: getLanguage('en'),
sizeRatio: 1, sizeRatio: 1,
openMenu: jest.fn(), openMenu: jest.genMockFunction(),
closeMenu: jest.fn(), closeMenu: jest.genMockFunction(),
showModal: jest.fn(), showModal: jest.genMockFunction(),
hideModal: jest.fn(), hideModal: jest.genMockFunction(),
tooltip: jest.fn(), tooltip: jest.genMockFunction(),
termtip: jest.fn(), termtip: jest.genMockFunction(),
onWindowResize: jest.fn() onWindowResize: jest.genMockFunction()
}; };
let modal, render, ContextProvider = Utils.createContextProvider(mockContext); let modal, render, ContextProvider = Utils.createContextProvider(mockContext);
@@ -110,25 +110,21 @@ describe('Import Modal', function() {
it('catches an invalid backup', function() { it('catches an invalid backup', function() {
const importData = require('./fixtures/valid-backup'); const importData = require('./fixtures/valid-backup');
let invalidImportData = Object.assign({}, importData); let invalidImportData = Object.assign({}, importData);
// Remove Asp Miner build used in comparison //invalidImportData.builds.asp = null; // Remove Asp Miner build used in comparison
delete(invalidImportData.builds.asp); delete(invalidImportData.builds.asp);
pasteText('"this is not valid"'); pasteText('"this is not valid"');
expect(modal.state.importValid).toBeFalsy(); expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual('Must be an object or array!'); expect(modal.state.errorMsg).toEqual('Must be an object or array!');
pasteText('{ "builds": "Should not be a string" }'); pasteText('{ "builds": "Should not be a string" }');
expect(modal.state.importValid).toBeFalsy(); expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual('builds must be an object!'); expect(modal.state.errorMsg).toEqual('builds must be an object!');
pasteText(JSON.stringify(importData).replace('anaconda', 'invalid_ship'));
pasteText(JSON.stringify(importData).replace(/anaconda/g, 'invalid_ship'));
expect(modal.state.importValid).toBeFalsy(); expect(modal.state.importValid).toBeFalsy();
expect(Object.keys(modal.state.builds)).not.toContain('anaconda'); expect(modal.state.errorMsg).toEqual('"invalid_ship" is not a valid Ship Id!');
pasteText(JSON.stringify(importData).replace('Dream', '')); pasteText(JSON.stringify(importData).replace('Dream', ''));
expect(modal.state.importValid).toBeFalsy(); expect(modal.state.importValid).toBeFalsy();
expect(Object.keys(modal.state.builds.imperial_clipper).length).toEqual(3); expect(modal.state.errorMsg).toEqual('Imperial Clipper build "" must be a string at least 1 character long!');
pasteText(JSON.stringify(invalidImportData)); pasteText(JSON.stringify(invalidImportData));
expect(modal.state.importValid).toBeFalsy(); expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual('asp build "Miner" data is missing!'); expect(modal.state.errorMsg).toEqual('asp build "Miner" data is missing!');
@@ -148,7 +144,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true); expect(modal.state.singleBuild).toBe(true);
clickProceed(); clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1); expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b-.AwRj4zNLeI%3D%3D.CwBhCYzBGW9qCTSqq5JA.&bn=Test%20My%20Ship'); expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.&bn=Test%20My%20Ship');
}); });
it('catches an invalid build', function() { it('catches an invalid build', function() {
@@ -173,7 +169,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true); expect(modal.state.singleBuild).toBe(true);
clickProceed(); clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1); expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b-.AwRj4zNLeI%3D%3D.CwBhCYzBGW9qCTSqq5JA.H4sIAAAAAAAAE2MUe8HMwPD%2FPwMAAGvB0AkAAAA%3D&bn=Test%20My%20Ship'); expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.H4sIAAAAAAAAA2MUe8HMwPD%2FPwMAAGvB0AkAAAA%3D&bn=Test%20My%20Ship');
}); });
}); });
@@ -190,7 +186,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true); expect(modal.state.singleBuild).toBe(true);
clickProceed(); clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1); expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/asp?code=A0pftiFflfddsnf5------020202033c044002v6-2i-.AwRj4yvYg%3D%3D%3D.CwRgDBldHn5A.H4sIAAAAAAAAE2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FA7kMAExxqlSAAAAA&bn=Multi-purpose%20Asp%20Explorer'); expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/asp?code=A0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FA7kMAExxqlSAAAAA&bn=Multi-purpose%20Asp%20Explorer');
}); });
it('imports a valid v4 build with modifications', function() { it('imports a valid v4 build with modifications', function() {
@@ -202,11 +198,11 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true); expect(modal.state.singleBuild).toBe(true);
clickProceed(); clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1); expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/imperial_courier?code=A0patzF5l0das8f31a1a270202000e402t0101----.AwRj4zOYg%3D%3D%3D.CwRgDBldLuZA.H4sIAAAAAAAAE12OPUvDYBSFT1OTfkRJjUkbbC3Yj8mlODgUISAtdOlety5ODv0Vgji7O7kJ%2FgzBQX%2BEY7Gg0NKhfY%2FnHQLFDBdynufe9%2BRMCmCb06g29oCgacjiRx6gY6oWKUT8UgLaszqQfHmSnpVFN1uSeXNsJVcj%2FA2EHlZkspIUpUc6UjTXGT85qwHuSEuVc%2F16r99kDQeSSjvSbSjpyUpNK10uJJ3aYqk6smwm1lQ9bOxw71TMm8VanEqq9JW1r3Qo%2BREOLnQHvbWmb7rZIu5VLIyGQGOukPv%2F0WQk5LeEAjPOUDwtAP6bShy2HKAz0HPO%2B5KsP25I79O2I7LvD%2Bz4Il1XAQAA&bn=Multi-purpose%20Imperial%20Courier'); expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/imperial_courier?code=A0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ%3D.H4sIAAAAAAAAA12OPUvDYBSFT1OTfkRJjUkbbC3Yj8mlODgUISAtdOlety5ODv0Vgji7O7kJ%2FgzBQX%2BEY7Gg0NKhfY%2FnHQLFDBdynufe9%2BRMCmCb06g29oCgacjiRx6gY6oWKUT8UgLaszqQfHmSnpVFN1uSeXNsJVcj%2FA2EHlZkspIUpUc6UjTXGT85qwHuSEuVc%2F16r99kDQeSSjvSbSjpyUpNK10uJJ3aYqk6smwm1lQ9bOxw71TMm8VanEqq9JW1r3Qo%2BREOLnQHvbWmb7rZIu5VLIyGQGOukPv%2F0WQk5LeEAjPOUDwtAP6bShy2HKAz0HPO%2B5KsP25I79O2I7LvD%2Bz4Il1XAQAA&bn=Multi-purpose%20Imperial%20Courier');
}); });
}); });
describe('Import Detailed Builds Array', function() { describe('Import Detaild Builds Array', function() {
beforeEach(reset); beforeEach(reset);
@@ -244,7 +240,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true); expect(modal.state.singleBuild).toBe(true);
clickProceed(); clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1); expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr--v66g--.AwRj4zNapI%3D%3D.CwRgDBldUExuBiIlWIA%3D.&bn=Imported%20Federal%20Corvette'); expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr--v66g2f.AwRj4zNaqA%3D%3D.CwRgDBldUExuBiIlUA%3D%3D.H4sIAAAAAAAAA12STy8DURTFb1szU53Ga8dg2qqqDmJDIoKFxJImumYjVrVqfAALC4lNbcUnkLCoDbEQu0bSlQVhI8JHsJBIQ73rXMkwMYuT9%2Bb87nl%2F7ovoRSL6ikD6TYNINZg5XsWUo7pfrBikr2USlRyXyDuLAhr6ZHanNLOzD5tjOiskysk5dOBvfTB7bjeRW0MNG3ohSBq1bKKxKwyLLUAjmwjpPu4wJx4xVbNI57heDfbUKUAy2xaRUQZpllHoHMHxKqjhhF4LgjtJiFHDmqbrEeVnUJOax7%2FSdRfRwBNotv9wo5kAuZMD2egKyDYcdYl1OBki6z%2BZQjaFnBPyFCM1LefF%2BcgrY0es9FKwbW8ZYj9gmBbxRVRdglMh6BNqnwsk4ouoO4HSIehNoBuBRHwR1QOmsBvHmk6IfMbd2fdCEka%2BjNSexPWGoEkcyX6CnxbxRZQtd%2BPpym%2B31xFtn0iSFPkf%2BBkttZlzB9KDFyBuFRfAGV0Ogoff8SSsCfjjD5hGWtLIwZB%2FgX5Zt%2BLHMI9My7sp6nzgZzekswTxVvCOkq%2FSXqb%2F3zfLxh6HrwIAAA%3D%3D&bn=Imported%20Federal%20Corvette');
}); });
it('imports a valid companion API build', function() { it('imports a valid companion API build', function() {
@@ -256,7 +252,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true); expect(modal.state.singleBuild).toBe(true);
clickProceed(); clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1); expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/beluga?code=A0pktsFplCdpsnf70t0t2727270004040404043c4fmimlmm04mc0iv62i--.AwRj4yusg%3D%3D%3D.CwRgDBldHi8IWIA%3D.&bn=Imported%20Beluga%20Liner'); expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/beluga?code=A0pktsFplCdpsnf70t0t2727270004040404043c4fmimlmm04mc0iv62i2f.AwRj4yukg%3D%3D%3D.CwRgDBldHi8IUA%3D%3D.H4sIAAAAAAAAA2P8Z8%2FAwPCXEUiIKTMxMPCv%2F%2Ff%2FP8cFIPGf6Z8YTEr0GjMDg%2FJWICERBOTzn%2Fn7%2F7%2FIO5Ai5n9SIEWsQEIoSxAolfbt%2F3%2BJPk4GBhE7YQYGYVmgcuVnf4Aq%2FwOVAAAyiFctbgAAAA%3D%3D&bn=Imported%20Beluga%20Liner');
}); });
it('imports a valid companion API build', function() { it('imports a valid companion API build', function() {
@@ -268,7 +264,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true); expect(modal.state.singleBuild).toBe(true);
clickProceed(); clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1); expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/type_7_transport?code=A0patfFflidasdf5----0404040005050504044d2402--.AwRj4yoo.CwRgDBlVK7HjEA%3D%3D.&bn=Imported%20Type-7%20Transporter'); expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/type_7_transport?code=A0patfFflidasdf5----0404040005050504044d2402.AwRj4yrI.CwRgDBlVK7EiA%3D%3D%3D.&bn=Imported%20Type-7%20Transporter');
}); });
it('imports a valid companion API build', function() { it('imports a valid companion API build', function() {
@@ -280,7 +276,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true); expect(modal.state.singleBuild).toBe(true);
clickProceed(); clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1); expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/cobra_mk_iii?code=A0p0tdFaldd3sdf4------34----2i--.AwRj4yqA.CwRgDMYExrezBig%3D.&bn=Imported%20Cobra%20Mk%20III'); expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/cobra_mk_iii?code=A0p0tdFaldd3sdf4------34---2f2i.AwRj4yKA.CwRgDMYExrezBUg%3D.&bn=Imported%20Cobra%20Mk%20III');
}); });
}); });
@@ -328,41 +324,4 @@ describe('Import Modal', function() {
}); });
}); });
describe('Imports SLEF data', () => {
beforeEach(reset);
it('imports a single valid SLEF build', () => {
const importData = require('./fixtures/slef-single-build.json');
pasteText(JSON.stringify(importData));
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/krait_mkii?code=A2pptkFflidussf52l1o1o2g2g020g040405051Ofr45C9C91oP3.Iw18eQ%3D%3D.AwRgzKIkA%3D%3D%3D.&bn=Imported%20pancake%20hammer');
});
it('imports multiple SLEF builds', () => {
const importData = require('./fixtures/slef-multiple-builds.json');
const expectedBuilds = require('./fixtures/slef-multiple-expected-builds.json');
pasteText(JSON.stringify(importData));
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
expect(modal.state.singleBuild).toBe(false);
clickProceed();
expect(modal.state.processed).toBeTruthy();
clickImport();
const builds = Persist.getBuilds();
for (const shipModel in builds) {
for (const buildName in builds[shipModel]) {
expect(builds[shipModel][buildName])
.toEqual(expectedBuilds[shipModel][buildName]);
}
}
});
});
}); });

11
d3-funcs.js vendored Normal file
View File

@@ -0,0 +1,11 @@
export {
axisBottom,
axisLeft,
axisTop,
formatLocale,
line,
scaleBand,
scaleLinear,
scaleOrdinal,
select
} from 'd3';

12477
d3.js vendored

File diff suppressed because it is too large Load Diff

4
d3.min.js vendored

File diff suppressed because one or more lines are too long

10
devServer.js Executable file → Normal file
View File

@@ -3,6 +3,7 @@ var WebpackDevServer = require("webpack-dev-server");
var config = require('./webpack.config.dev'); var config = require('./webpack.config.dev');
new WebpackDevServer(webpack(config), { new WebpackDevServer(webpack(config), {
publicPath: config.output.publicPath,
hot: true, hot: true,
headers: { "Access-Control-Allow-Origin": "*" }, headers: { "Access-Control-Allow-Origin": "*" },
historyApiFallback: { historyApiFallback: {
@@ -10,6 +11,15 @@ new WebpackDevServer(webpack(config), {
// For some reason connect-history-api-fallback does not allow '.' in the URL for history fallback... // For some reason connect-history-api-fallback does not allow '.' in the URL for history fallback...
{ from: /\/outfit\//, to: '/index.html' } { from: /\/outfit\//, to: '/index.html' }
] ]
},
stats: {
assets: true,
colors: true,
version: false,
hash: false,
timings: true,
chunks: false,
chunkModules: false
} }
}).listen(3300, "0.0.0.0", function (err, result) { }).listen(3300, "0.0.0.0", function (err, result) {
if (err) { if (err) {

59
nginx.conf Normal file
View File

@@ -0,0 +1,59 @@
worker_processes 2;
error_log ./nginx.error.log;
worker_rlimit_nofile 8192;
pid nginx.pid;
events {
worker_connections 1024;
multi_accept on;
}
http {
access_log off;
charset UTF-8;
types {
text/html html htm shtml;
text/css css;
text/xml xml rss;
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;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
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 {
listen 3301;
server_name localhost;
root ./build/;
index index.html;
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;
}
}
}

22402
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,17 @@
{ {
"name": "coriolis_shipyard", "name": "coriolis_shipyard",
"version": "3.0.1", "version": "2.5.1",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/EDCD/coriolis" "url": "https://github.com/EDCD/coriolis"
}, },
"homepage": "https://coriolis.io", "homepage": "https://coriolis.edcd.io",
"bugs": "https://github.com/EDCD/coriolis/issues", "bugs": "https://github.com/EDCD/coriolis/issues",
"contributors": [
{ "name": "cmdrmcdonald" },
{ "name": "willb321" },
{ "name": "felixlinker" }
],
"private": true, "private": true,
"engine": "node >= 10.13.0", "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",
@@ -23,8 +19,7 @@
"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)",
"buildfresh": "rimraf node_modules && rm package-lock.json && npm install && npm run build > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)", "build": "npm run clean && NODE_ENV=production webpack -p --config webpack.config.prod.js",
"build": "npm run clean && cross-env NODE_ENV=production webpack --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"
}, },
@@ -61,101 +56,59 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.12", "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", "css-loader": "^0.28.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", "d3-selection": "1",
"@babel/plugin-proposal-numeric-separator": "^7.0.0", "eslint": "3.19.0",
"@babel/plugin-proposal-optional-chaining": "^7.0.0", "eslint-plugin-react": "^6.10.3",
"@babel/plugin-proposal-pipeline-operator": "^7.0.0", "expose-loader": "^0.7.3",
"@babel/plugin-proposal-throw-expressions": "^7.0.0", "express": "^4.15.2",
"@babel/plugin-syntax-dynamic-import": "^7.0.0", "extract-text-webpack-plugin": "2.1.0",
"@babel/plugin-syntax-import-meta": "^7.0.0", "file-loader": "^0.11.1",
"@babel/preset-env": "^7.0.0", "html-webpack-plugin": "^2.28.0",
"@babel/preset-react": "^7.0.0", "jest-cli": "^21.2.1",
"@rollup/plugin-node-resolve": "^15.0.1", "jsen": "^0.6.4",
"babel-loader": "^8.0.0",
"copy-webpack-plugin": "^10.2.4",
"create-react-class": "^15.6.3",
"cross-env": "^5.2.0",
"css-loader": "^6.7.3",
"d3-selection": "^1.3.2",
"esdoc": "^1.1.0",
"esdoc-custom-theme": "^1.4.2",
"esdoc-ecmascript-proposal-plugin": "^1.0.0",
"esdoc-jsx-plugin": "^1.0.0",
"esdoc-publish-html-plugin": "^1.1.2",
"esdoc-react-plugin": "^1.0.1",
"esdoc-standard-plugin": "^1.0.0",
"eslint": "^5.6.0",
"eslint-plugin-react": "^7.11.1",
"expose-loader": "^3.1.0",
"express": "^4.18.2",
"html-webpack-plugin": "^5.5.0",
"jsen": "^0.6.6",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"less": "^3.8.1", "less": "^2.7.2",
"less-loader": "^11.1.0", "less-loader": "^4.0.3",
"mini-css-extract-plugin": "^2.7.2", "react-addons-perf": "^15.4.2",
"react-container-dimensions": "^1.4.1", "react-measure": "^1.4.7",
"react-testutils-additions": "^15.0.0", "react-testutils-additions": "^15.2.0",
"react-transition-group": "^2.5.0", "react-transition-group": "^1.1.2",
"rimraf": "^4.1.2", "rimraf": "^2.6.1",
"rollup": "^3.17.2", "rollup": "0.41",
"style-loader": "^3.3.1", "rollup-plugin-node-resolve": "3",
"uglify-js": "^3.17.4", "style-loader": "^0.16.1",
"webpack": "^5.75.0", "url-loader": "^0.5.8",
"webpack-cli": "^5.0.1", "webpack": "^2.4.1",
"webpack-dev-server": "^4.11.1", "webpack-dev-server": "^2.4.4",
"webpack-merge": "^5.8.0", "uglify-js": "^2.4.11"
"webpack-notifier": "^1.15.0",
"workbox-cacheable-response": "^6.5.4",
"workbox-expiration": "^6.5.4",
"workbox-precaching": "^6.5.4",
"workbox-routing": "^6.5.4",
"workbox-strategies": "^6.5.4",
"workbox-webpack-plugin": "^6.5.4"
}, },
"sideEffects": false,
"dependencies": { "dependencies": {
"assert": "^1.5.0", "axios": "^0.17.1",
"auto-bind": "^5.0.1", "babel-polyfill": "*",
"base64url": "^3.0.1",
"browserify-zlib-next": "^1.0.1", "browserify-zlib-next": "^1.0.1",
"buffer": "^5.7.0", "classnames": "^2.2.5",
"classnames": "^2.2.6",
"constants-browserify": "^1.0.0",
"core-js": "^3.28.0",
"coriolis-data": "../coriolis-data", "coriolis-data": "../coriolis-data",
"crypto-browserify": "^3.12.0", "d3": "4.8.0",
"d3": "^5.7.0", "detect-browser": "^1.7.0",
"detect-browser": "^3.0.1",
"fbemitter": "^2.1.1", "fbemitter": "^2.1.1",
"https-browserify": "^1.0.0", "lodash": "^4.17.4",
"lodash": "^4.17.11",
"lz-string": "^1.4.4", "lz-string": "^1.4.4",
"os-browserify": "^0.3.0", "pako": "^1.0.6",
"pako": "^2.1.0", "prop-types": "^15.5.8",
"path-browserify": "^1.0.1", "react": "^15.5.4",
"prop-types": "^15.6.2", "react-dom": "^15.5.4",
"react": "^15.6.2", "react-number-editor": "Athanasius/react-number-editor.git#miggy",
"react-dom": "^15.6.2", "recharts": "^0.22.3",
"react-fuzzy": "^0.5.2", "superagent": "^3.5.2"
"react-ga": "^2.5.3",
"react-number-editor": "^4.0.3",
"recharts": "^1.2.0",
"register-service-worker": "^1.7.2",
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
"superagent": "^3.8.3",
"url": "^0.11.0",
"vm-browserify": "^1.1.2"
} }
} }

View File

@@ -1,4 +1,4 @@
import nodeResolve from "@rollup/plugin-node-resolve"; import nodeResolve from "rollup-plugin-node-resolve";
export default { export default {
entry: "d3-funcs.js", entry: "d3-funcs.js",

View File

@@ -214,7 +214,7 @@ Options -MultiViews
# </Files> # </Files>
AddType application/x-web-app-manifest+json webapp AddType application/x-web-app-manifest+json webapp
# AddType text/cache-manifest appcache manifest AddType text/cache-manifest appcache manifest
# Media files # Media files
AddType audio/mp4 f4a f4b m4a AddType audio/mp4 f4a f4b m4a

View File

@@ -1,12 +1,10 @@
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 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 ModalExport from './components/ModalExport';
@@ -14,7 +12,7 @@ import ModalHelp from './components/ModalHelp';
import ModalImport from './components/ModalImport'; import ModalImport from './components/ModalImport';
import ModalPermalink from './components/ModalPermalink'; import ModalPermalink from './components/ModalPermalink';
import * as CompanionApiUtils from './utils/CompanionApiUtils'; import * as CompanionApiUtils from './utils/CompanionApiUtils';
import * as JournalUtils from './utils/JournalUtils';
import AboutPage from './pages/AboutPage'; import AboutPage from './pages/AboutPage';
import NotFoundPage from './pages/NotFoundPage'; import NotFoundPage from './pages/NotFoundPage';
import OutfittingPage from './pages/OutfittingPage'; import OutfittingPage from './pages/OutfittingPage';
@@ -22,14 +20,13 @@ 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 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,
@@ -68,11 +65,11 @@ export default class Coriolis extends React.Component {
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()
}; };
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));
@@ -93,34 +90,12 @@ 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.from(r.params.data, 'base64'), { to: 'string' }); const data = zlib.inflate(new Buffer(r.params.data, 'base64'), { to: 'string' });
const json = JSON.parse(data); const json = JSON.parse(data);
console.info('Ship import data: '); const ship = CompanionApiUtils.shipFromJson(json);
console.info(json); r.params.ship = ship.id;
let ship, importString; r.params.code = ship.toString();
if (json) { this._setPage(OutfittingPage, r);
if (json.length && json[0].data) { // SLEF
if (json.length > 1) { // Multiple builds, open modal
importString = data;
} else { // Single build, import directly
ship = JournalUtils.shipFromLoadoutJSON(json[0].data);
}
} else { // not SLEF
if (json.modules) {
ship = CompanionApiUtils.shipFromJson(json);
} else if (json.Modules) {
ship = JournalUtils.shipFromLoadoutJSON(json);
}
}
}
if (ship) {
r.params.ship = ship.id;
r.params.code = ship.toString();
this._setPage(OutfittingPage, r);
} else if (importString) {
this._setPage(ShipyardPage, r);
this._showModal(<ModalImport importString={data}/>);
}
} 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);
} }
@@ -149,6 +124,13 @@ export default class Coriolis extends React.Component {
*/ */
_onError(msg, scriptUrl, line, col, errObj) { _onError(msg, scriptUrl, line, col, errObj) {
console && console.error && console.error(arguments); // eslint-disable-line no-console console && console.error && console.error(arguments); // eslint-disable-line no-console
if (errObj) {
if (errObj instanceof Error) {
Bugsnag.notifyException(errObj) // eslint-disable-line
} else if (errObj instanceof String) {
Bugsnag.notify(msg, errObj) // eslint-disable-line
}
}
this.setState({ this.setState({
error: <ErrorDetails error={{ message: msg, details: { scriptUrl, line, col, error: JSON.stringify(errObj) } }}/>, error: <ErrorDetails error={{ message: msg, details: { scriptUrl, line, col, error: JSON.stringify(errObj) } }}/>,
page: null, page: null,
@@ -190,13 +172,13 @@ export default class Coriolis extends React.Component {
case 72: // 'h' case 72: // 'h'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + h if (e.ctrlKey || e.metaKey) { // CTRL/CMD + h
e.preventDefault(); e.preventDefault();
this._showModal(<ModalHelp/>); this._showModal(<ModalHelp />);
} }
break; break;
case 73: // 'i' case 73: // 'i'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i
e.preventDefault(); e.preventDefault();
this._showModal(<ModalImport/>); this._showModal(<ModalImport />);
} }
break; break;
case 79: // 'o' case 79: // 'o'
@@ -218,7 +200,7 @@ export default class Coriolis extends React.Component {
* @param {React.Component} content Modal Content * @param {React.Component} content Modal Content
*/ */
_showModal(content) { _showModal(content) {
let modal = <div className='modal-bg' onClick={(e) => this._hideModal()}>{content}</div>; let modal = <div className='modal-bg' onClick={(e) => this._hideModal() }>{content}</div>;
this.setState({ modal }); this.setState({ modal });
} }
@@ -296,7 +278,7 @@ export default class Coriolis extends React.Component {
return this.emitter.addListener('windowResize', listener); return this.emitter.addListener('windowResize', listener);
} }
/** /**
* Add a listener to global commands such as save, * Add a listener to global commands such as save,
* @param {Function} listener Listener callback * @param {Function} listener Listener callback
* @return {Object} Subscription token * @return {Object} Subscription token
@@ -332,50 +314,14 @@ export default class Coriolis extends React.Component {
*/ */
componentWillMount() { componentWillMount() {
// Listen for appcache updated event, present refresh to update view // Listen for appcache updated event, present refresh to update view
// Check that service workers are registered if (window.applicationCache) {
if (navigator.storage && navigator.storage.persist) { window.applicationCache.addEventListener('updateready', () => {
window.addEventListener('load', () => { if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
navigator.storage.persist().then(granted => { this.setState({ appCacheUpdate: true }); // Browser downloaded a new app cache.
if (granted) }
console.log('Storage will not be cleared except by explicit user action');
else
console.log('Storage may be cleared by the UA under storage pressure.');
});
}); });
} }
if ('serviceWorker' in navigator) {
// Your service-worker.js *must* be located at the top-level directory relative to your site.
// It won't be able to control pages unless it's located at the same level or higher than them.
// *Don't* register service worker file in, e.g., a scripts/ sub-directory!
// See https://github.com/slightlyoff/ServiceWorker/issues/468
const self = this;
if (process.env.NODE_ENV === 'production') {
register('/service-worker.js', {
ready(registration) {
console.log('Service worker is active.');
},
registered(registration) {
console.log('Service worker has been registered.');
},
cached(registration) {
console.log('Content has been cached for offline use.');
},
updatefound(registration) {
console.log('New content is downloading.');
},
updated(registration) {
self.setState({ appCacheUpdate: true });
console.log('New content is available; please refresh.');
},
offline() {
console.log('No internet connection found. App is running in offline mode.');
},
error(error) {
console.error('Error during service worker registration:', error);
}
});
}
}
window.onerror = this._onError.bind(this); window.onerror = this._onError.bind(this);
window.addEventListener('resize', () => this.emitter.emit('windowResize')); window.addEventListener('resize', () => this.emitter.emit('windowResize'));
document.getElementById('coriolis').addEventListener('scroll', () => this._tooltip()); document.getElementById('coriolis').addEventListener('scroll', () => this._tooltip());
@@ -392,27 +338,15 @@ export default class Coriolis extends React.Component {
*/ */
render() { render() {
let currentMenu = this.state.currentMenu; let currentMenu = this.state.currentMenu;
return <div style={{ minHeight: '100%' }} onClick={this._closeMenu}
className={this.state.noTouch ? 'no-touch' : null}>
<Header announcements={this.state.announcements} appCacheUpdate={this.state.appCacheUpdate}
currentMenu={currentMenu}/>
<div className="announcement-container">{this.state.announcements.map(a => <Announcement
text={a.text}/>)}</div>
{this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) :
<NotFoundPage/>}
{this.state.modal}
{this.state.tooltip}
<footer>
return <div style={{ minHeight: '100%' }} onClick={this._closeMenu} className={ this.state.noTouch ? 'no-touch' : null }>
<Header appCacheUpdate={this.state.appCacheUpdate} currentMenu={currentMenu} />
{ this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) : <NotFoundPage/> }
{ this.state.modal }
{ this.state.tooltip }
<footer>
<div className="right cap"> <div className="right cap">
<a href="https://github.com/EDCD/coriolis" target="_blank" rel="noopener noreferrer" <a href="https://github.com/EDCD/coriolis" target="_blank" title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
<br/>
<a
href={'https://github.com/EDCD/coriolis/compare/edcd:develop@{' + window.CORIOLIS_DATE + '}...edcd:develop'}
target="_blank" rel="noopener noreferrer" title={'Coriolis Commits since' + window.CORIOLIS_DATE}>Commits
since last release
({window.CORIOLIS_DATE})</a>
</div> </div>
</footer> </footer>
</div>; </div>;

View File

@@ -72,7 +72,6 @@ Router.go = function(path, state) {
gaTrack(path); gaTrack(path);
let ctx = new Context(path, state); let ctx = new Context(path, state);
Router.dispatch(ctx); Router.dispatch(ctx);
if (!ctx.unhandled) { if (!ctx.unhandled) {
if (isStandAlone()) { if (isStandAlone()) {
Persist.setState(ctx); Persist.setState(ctx);
@@ -258,8 +257,9 @@ Route.prototype.match = function(path, params) {
* @param {string} path Path to track * @param {string} path Path to track
*/ */
function gaTrack(path) { function gaTrack(path) {
const _paq = window._paq || []; if (window.ga) {
_paq.push(['trackPageView']); window.ga('send', 'pageview', path);
}
} }
/** /**

View File

@@ -16,6 +16,7 @@ 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
@@ -28,4 +29,5 @@ 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>;
} }
}
}

View File

@@ -1,31 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import autoBind from 'auto-bind';
/**
* 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>;
}
}

View File

@@ -4,8 +4,7 @@ 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 { CoriolisLogo, MountFixed, MountGimballed, MountTurret } from './SvgIcons'; import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import FuzzySearch from 'react-fuzzy';
const PRESS_THRESHOLD = 500; // mouse/touch down threshold const PRESS_THRESHOLD = 500; // mouse/touch down threshold
@@ -20,7 +19,6 @@ const GRPCAT = {
'cc': 'limpet controllers', 'cc': 'limpet controllers',
'fx': 'limpet controllers', 'fx': 'limpet controllers',
'hb': 'limpet controllers', 'hb': 'limpet controllers',
'mlc': 'limpet controllers',
'pc': 'limpet controllers', 'pc': 'limpet controllers',
'rpl': 'limpet controllers', 'rpl': 'limpet controllers',
'pce': 'passenger cabins', 'pce': 'passenger cabins',
@@ -39,24 +37,13 @@ const GRPCAT = {
'ml': 'lasers', 'ml': 'lasers',
'c': 'projectiles', 'c': 'projectiles',
'mc': 'projectiles', 'mc': 'projectiles',
'advmc': 'projectiles',
'axmc': 'experimental', 'axmc': 'experimental',
'axmce': 'experimental',
'ntp': 'experimental',
'fc': 'projectiles', 'fc': 'projectiles',
'rfl': 'experimental', 'rfl': 'experimental',
'pa': 'projectiles', 'pa': 'projectiles',
'rg': 'projectiles', 'rg': 'projectiles',
'mr': 'ordnance', 'mr': 'ordnance',
'axmr': 'experimental', 'axmr': 'experimental',
'axmre': 'experimental',
'rcpl': 'experimental',
'dtl': 'experimental',
'tbsc': 'experimental',
'tbem': 'experimental',
'tbrfl': 'experimental',
'mahr': 'experimental',
'rsl': 'experimental',
'tp': 'ordnance', 'tp': 'ordnance',
'nl': 'ordnance', 'nl': 'ordnance',
'sc': 'scanners', 'sc': 'scanners',
@@ -69,27 +56,7 @@ const GRPCAT = {
'ch': 'defence', 'ch': 'defence',
'po': 'defence', 'po': 'defence',
'ec': 'defence', 'ec': 'defence',
'sfn': 'defence', 'sfn': 'defence'
// Guardian
'gpp': 'guardian',
'gpc': 'guardian',
'gsrp': 'guardian',
'ggc': 'guardian',
'gfsb': 'guardian',
'gmrp': 'guardian',
'gsc': 'guardian',
'ghrp': 'guardian',
// Mining
'scl': 'mining',
'pwa': 'mining',
'sdm': 'mining',
// Assists
'dc': 'flight assists',
'sua': 'flight assists',
// Stabilizers
'ews': 'weapon stabilizers',
}; };
// Order here is the order in which items will be shown in the modules menu // Order here is the order in which items will be shown in the modules menu
const CATEGORIES = { const CATEGORIES = {
@@ -99,47 +66,41 @@ const CATEGORIES = {
'fi': ['fi'], 'fi': ['fi'],
'fuel': ['ft', 'fs'], 'fuel': ['ft', 'fs'],
'hangars': ['fh', 'pv'], 'hangars': ['fh', 'pv'],
'limpet controllers': ['cc', 'fx', 'hb', 'pc', 'rpl', 'mlc'], 'limpet controllers': ['cc', 'fx', 'hb', 'pc', 'rpl'],
'passenger cabins': ['pce', 'pci', 'pcm', 'pcq'], 'passenger cabins': ['pce', 'pci', 'pcm', 'pcq'],
'rf': ['rf'], 'rf': ['rf'],
'shields': ['sg', 'bsg', 'psg', 'scb'], 'shields': ['sg', 'bsg', 'psg', 'scb'],
'structural reinforcement': ['hr', 'mrp'], 'structural reinforcement': ['hr', 'mrp'],
'flight assists': ['dc', 'sua'], 'dc': ['dc'],
// Hardpoints // Hardpoints
'lasers': ['pl', 'ul', 'bl'], 'lasers': ['pl', 'ul', 'bl', 'ml'],
'projectiles': ['mc', 'advmc', 'c', 'fc', 'pa', 'rg'], 'projectiles': ['mc', 'c', 'fc', 'pa', 'rg'],
'ordnance': ['mr', 'tp', 'nl'], 'ordnance': ['mr', 'tp', 'nl'],
// Utilities // Utilities
'sb': ['sb'], 'sb': ['sb'],
'hs': ['hs'], 'hs': ['hs'],
'csl': ['csl'],
'defence': ['ch', 'po', 'ec'], 'defence': ['ch', 'po', 'ec'],
'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners 'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners
// Experimental // Experimental
'experimental': ['axmc', 'axmce', 'axmr', 'axmre', 'ntp','rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr',], 'experimental': ['axmc', 'axmr', 'rfl', 'xs', 'sfn']
'weapon stabilizers': ['ews'],
// Guardian
'guardian': ['gpp', 'gpd', 'gpc', 'ggc', 'gsrp', 'gfsb', 'ghrp', 'gmrp', 'gsc'],
'mining': ['ml', 'scl', 'pwa', 'sdm', 'abl'],
}; };
/** /**
* 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, modules: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
onSelect: PropTypes.func.isRequired, onSelect: PropTypes.func.isRequired,
diffDetails: PropTypes.func, diffDetails: PropTypes.func,
m: PropTypes.object, m: PropTypes.object,
ship: PropTypes.object.isRequired, shipMass: PropTypes.number,
warning: PropTypes.func, warning: PropTypes.func
firstSlotId: PropTypes.string, };
lastSlotId: PropTypes.string,
activeSlotId: PropTypes.string, static defaultProps = {
slotDiv: PropTypes.object shipMass: 0
}; };
/** /**
@@ -150,9 +111,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
constructor(props, context) { constructor(props, context) {
super(props); super(props);
this._hideDiff = this._hideDiff.bind(this); this._hideDiff = this._hideDiff.bind(this);
this._showSearch = this._showSearch.bind(this);
this.state = this._initState(props, context); this.state = this._initState(props, context);
this.slotItems = [];// Array to hold <li> refs.
} }
/** /**
@@ -163,33 +122,27 @@ export default class AvailableModulesMenu extends TranslatedComponent {
*/ */
_initState(props, context) { _initState(props, context) {
let translate = context.language.translate; let translate = context.language.translate;
let { m, warning, onSelect, modules, ship } = props; let { m, warning, shipMass, onSelect, modules } = props;
let list, currentGroup; let list, currentGroup;
let buildGroup = this._buildGroup.bind( let buildGroup = this._buildGroup.bind(
this, this,
ship,
translate, translate,
m, m,
warning, warning,
shipMass - (m && m.mass ? m.mass : 0),
(m, event) => { (m, event) => {
this._hideDiff(event); this._hideDiff(event);
onSelect(m); onSelect(m);
} }
); );
let fuzzy = [];
if (modules instanceof Array) { if (modules instanceof Array) {
list = buildGroup(modules[0].grp, modules); list = buildGroup(modules[0].grp, modules);
} else { } else {
list = []; list = [];
// At present time slots with grouped options (Hardpoints and Internal) can be empty // At present time slots with grouped options (Hardpoints and Internal) can be empty
if (m) { if (m) {
let emptyId = 'empty'; list.push(<div className='empty-c upp' key='empty' onClick={onSelect.bind(null, null)} >{translate('empty')}</div>);
if (this.firstSlotId == null) this.firstSlotId = emptyId;
let keyDown = this._keyDown.bind(this, onSelect);
list.push(<div className='empty-c upp' key={emptyId} data-id={emptyId} onClick={onSelect.bind(null, null)}
onKeyDown={keyDown} tabIndex="0"
ref={slotItem => this.slotItems[emptyId] = slotItem}>{translate('empty')}</div>);
} }
// Need to regroup the modules by our own categorisation // Need to regroup the modules by our own categorisation
@@ -217,125 +170,67 @@ export default class AvailableModulesMenu extends TranslatedComponent {
if (categories.length === 1) { if (categories.length === 1) {
// Show category header instead of group header // Show category header instead of group header
if (m && grp == m.grp) { if (m && grp == m.grp) {
// If this is a missing module/weapon, skip it list.push(<div ref={(elem) => this.groupElem = elem} key={category} className={'select-category upp'}>{translate(category)}</div>);
if (m.grp == "mh" || m.grp == "mm"){
continue;
} else {
list.push(<div ref={(elem) => this.groupElem = elem} key={category}
className={'select-category upp'}>{translate(category)}</div>);
}
} else { } else {
if (category == "mh" || category == "mm"){ list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
continue;
} else {
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
}
} }
} else { } else {
// Show category header as well as group header // Show category header as well as group header
if (!categoryHeader) { if (!categoryHeader) {
if (category == "mh" || category == "mm"){ list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
continue; categoryHeader = true;
}
else {
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
categoryHeader = true;
}
} }
if (m && grp == m.grp) { if (m && grp == m.grp) {
list.push(<div ref={(elem) => this.groupElem = elem} key={grp} list.push(<div ref={(elem) => this.groupElem = elem} key={grp} className={'select-group cap'}>{translate(grp)}</div>);
className={'select-group cap'}>{translate(grp)}</div>);
} else { } else {
list.push(<div key={grp} className={'select-group cap'}>{translate(grp)}</div>); list.push(<div key={grp} className={'select-group cap'}>{translate(grp)}</div>);
} }
} }
list.push(buildGroup(grp, modules[grp])); list.push(buildGroup(grp, modules[grp]));
for (const i of modules[grp]) {
let mount = '';
if (i.mount === 'F') {
mount = 'Fixed';
} else if (i.mount === 'G') {
mount = 'Gimballed';
} else if (i.mount === 'T') {
mount = 'Turreted';
}
const fuzz = { grp, m: i, name: `${i.class}${i.rating}${mount ? ' ' + mount : ''} ${translate(grp)}` };
fuzzy.push(fuzz);
}
} }
} }
} }
} }
} }
let trackingFocus = false;
return { list, currentGroup, fuzzy, trackingFocus };
}
/** return { list, currentGroup };
* Return Is expiremental capacity reached
* @return {boolean} Is experimental capacity reached
*/
_experimentalCapacityReached() {
const ship = this.props.ship;
const ews = ship.internal.filter(o => o.m && o.m.grp === 'ews');
let expCap;
if(ews.length < 1){
expCap = 4;
} else{
expCap = ews[0].m.class == 3 ? 5 : 6;
}
return expCap <= this.props.ship.hardpoints.filter(o => o.m && o.m.experimental).length;
} }
/** /**
* Generate React Components for Module Group * Generate React Components for Module Group
* @param {Ship} ship Ship the selection is for
* @param {Function} translate Translate function * @param {Function} translate Translate function
* @param {Object} mountedModule Mounted Module * @param {Objecy} mountedModule Mounted Module
* @param {Function} warningFunc Warning function * @param {Funciton} warningFunc Warning function
* @param {number} mass Mass
* @param {function} onSelect Select/Mount callback * @param {function} onSelect Select/Mount callback
* @param {string} grp Group name * @param {string} grp Group name
* @param {Array} modules Available modules * @param {Array} modules Available modules
* @return {React.Component} Available Module Group contents * @return {React.Component} Available Module Group contents
*/ */
_buildGroup(ship, translate, mountedModule, warningFunc, onSelect, grp, modules) { _buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules) {
let prevClass = null, prevRating = null, prevName; let prevClass = null, prevRating = null;
let elems = []; let elems = [];
const sortedModules = modules.sort(this._moduleOrder); const sortedModules = modules.sort(this._moduleOrder);
// Calculate the number of items per class. Used so we don't have long lists with only a few items in each row // Calculate the number of items per class. Used so we don't have long lists with only a few items in each row
const tmp = sortedModules.map((v, i) => v['class']).reduce((count, cls) => { const tmp = sortedModules.map((v, i) => v['class']).reduce((count, cls) => { count[cls] = ++count[cls] || 1; return count; }, {});
count[cls] = ++count[cls] || 1;
return count;
}, {});
const itemsPerClass = Math.max.apply(null, Object.keys(tmp).map(key => tmp[key])); const itemsPerClass = Math.max.apply(null, Object.keys(tmp).map(key => tmp[key]));
let itemsOnThisRow = 0; let itemsOnThisRow = 0;
for (let i = 0; i < sortedModules.length; i++) { for (let i = 0; i < sortedModules.length; i++) {
let m = sortedModules[i]; let m = sortedModules[i];
// If m.grp is mh or mm, or m.symbol contains 'Missing' skip it
if (m.grp == 'mh' || m.grp == 'mm' || m.symbol.includes("Missing")) {
// If this is a missing module, skip it
continue;
}
let mount = null; let mount = null;
let disabled = false; let disabled = false;
prevName = m.name;
if (ModuleUtils.isShieldGenerator(m.grp)) { if (ModuleUtils.isShieldGenerator(m.grp)) {
// Shield generators care about maximum hull mass // Shield generators care about maximum hull mass
disabled = ship.hullMass > m.maxmass; disabled = mass > m.maxmass;
// If the mounted module is experimental as well, we can replace it so } else if (m.maxmass) {
// the maximum does not apply // Thrusters care about total mass
} else if (m.experimental && (!mountedModule || !mountedModule.experimental)) { disabled = mass + m.mass > m.maxmass;
disabled = this._experimentalCapacityReached();
} else if (m.grp === 'mlc' && (!mountedModule || mountedModule.grp !== 'mlc')) {
disabled = 1 <= ship.internal.filter(o => o.m && o.m.grp === 'mlc').length;
} }
let active = mountedModule && mountedModule.id === m.id; let active = mountedModule && mountedModule.id === m.id;
let classes = cn(m.name ? 'lc' : 'c', { let classes = cn(m.name ? 'lc' : 'c', {
warning: !disabled && warningFunc && warningFunc(m), warning: !disabled && warningFunc && warningFunc(m),
active, active,
@@ -343,21 +238,9 @@ export default class AvailableModulesMenu extends TranslatedComponent {
}); });
let eventHandlers; let eventHandlers;
if (disabled) { if (disabled || active) {
eventHandlers = { eventHandlers = {};
onKeyDown: this._keyDown.bind(this, null),
onKeyUp: this._keyUp.bind(this, null)
};
} else { } else {
/**
* Get the ids of the first and last <li> elements in the <ul> that are focusable (i.e. are not active or disabled)
* Will be used to keep focus inside the <ul> on Tab and Shift-Tab while it is visible
*/
if (this.firstSlotId == null) this.firstSlotId = sortedModules[i].id;
if (active) this.activeSlotId = sortedModules[i].id;
this.lastSlotId = sortedModules[i].id;
let showDiff = this._showDiff.bind(this, mountedModule, m); let showDiff = this._showDiff.bind(this, mountedModule, m);
let select = onSelect.bind(null, m); let select = onSelect.bind(null, m);
@@ -366,46 +249,33 @@ export default class AvailableModulesMenu extends TranslatedComponent {
onTouchStart: this._touchStart.bind(this, showDiff), onTouchStart: this._touchStart.bind(this, showDiff),
onTouchEnd: this._touchEnd.bind(this, select), onTouchEnd: this._touchEnd.bind(this, select),
onMouseLeave: this._hideDiff, onMouseLeave: this._hideDiff,
onClick: select, onClick: select
onKeyDown: this._keyDown.bind(this, select),
onKeyUp: this._keyUp.bind(this, select)
}; };
} }
switch (m.mount) { switch(m.mount) {
case 'F': case 'F': mount = <MountFixed className={'lg'} />; break;
mount = <MountFixed className={'lg'}/>; case 'G': mount = <MountGimballed className={'lg'}/>; break;
break; case 'T': mount = <MountTurret 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)) { if (itemsOnThisRow == 6 || i > 0 && sortedModules.length > 3 && itemsPerClass > 2 && m.class != prevClass && (m.rating != prevRating || m.mount)) {
elems.push(<br key={'b' + m.grp + i}/>); elems.push(<br key={'b' + m.grp + i} />);
itemsOnThisRow = 0; itemsOnThisRow = 0;
} }
let tbIdx = (classes.indexOf('disabled') < 0) ? 0 : undefined;
elems.push( elems.push(
<li key={m.id} data-id={m.id} className={classes} {...eventHandlers} tabIndex={tbIdx} <li key={m.id} className={classes} {...eventHandlers}>
ref={slotItem => this.slotItems[m.id] = slotItem}>
{mount} {mount}
{(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')} {(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')}
</li> </li>
); );
itemsOnThisRow++; itemsOnThisRow++;
prevClass = m.class; prevClass = m.class;
prevRating = m.rating; prevRating = m.rating;
prevName = m.name;
} }
return <ul key={'modules' + grp}>{elems}</ul>;
return <ul key={'modules' + grp} >{elems}</ul>;
} }
/** /**
@@ -422,50 +292,6 @@ export default class AvailableModulesMenu extends TranslatedComponent {
} }
} }
/**
* Generate tooltip content for the difference between the
* mounted module and the hovered modules
*/
_showSearch() {
if (this.props.modules instanceof Array) {
return;
}
const mountedModule = this.props.m;
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) => {
let disabled;
if(val.m.experimental && (!mountedModule || !mountedModule.experimental)) {
disabled = this._experimentalCapacityReached();
} else{
disabled = false;
}
const handler = disabled ? null : () => clickHandler(i);
return (
<div
key={i}
className={cn('lc', {disabled})}
onClick={handler}
>
{val.name}
</div>
);
});
}}
/>
);
}
/** /**
* Mouse over diff handler * Mouse over diff handler
* @param {Function} showDiff diff tooltip callback * @param {Function} showDiff diff tooltip callback
@@ -500,41 +326,6 @@ export default class AvailableModulesMenu extends TranslatedComponent {
this._hideDiff(); this._hideDiff();
} }
/**
* Key down - select module on Enter key, move to next/previous module on Tab/Shift-Tab, close on Esc
* @param {Function} select Select module callback
* @param {SyntheticEvent} event Event
*/
_keyDown(select, event) {
let className = event.currentTarget.attributes['class'].value;
if (event.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) {
select();
return;
}
let elemId = event.currentTarget.attributes['data-id'].value;
if (className.indexOf('disabled') < 0 && event.key == 'Tab') {
if (event.shiftKey && elemId == this.firstSlotId) {
event.preventDefault();
this.slotItems[this.lastSlotId].focus();
return;
}
if (!event.shiftKey && elemId == this.lastSlotId) {
event.preventDefault();
this.slotItems[this.firstSlotId].focus();
return;
}
}
}
/**
* Key Up
* @param {Function} select Select module callback
* @param {SytheticEvent} event Event
*/
_keyUp(select, event) {
// nothing here yet
}
/** /**
* Hide diff tooltip * Hide diff tooltip
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
@@ -574,15 +365,6 @@ export default class AvailableModulesMenu extends TranslatedComponent {
return 1; return 1;
} }
} }
// Sort multi limpet controllers by name
if (a.grp === 'mlc') {
if (a.name[0] <= b.name[0]) {
return -1;
}
if (a.name[0] > b.name[0]) {
return 1;
}
}
// Rating ordered from highest (A) to lowest (E) // Rating ordered from highest (A) to lowest (E)
if (a.rating < b.rating) { if (a.rating < b.rating) {
return -1; return -1;
@@ -601,24 +383,6 @@ export default class AvailableModulesMenu extends TranslatedComponent {
if (this.groupElem) { // Scroll to currently selected group if (this.groupElem) { // Scroll to currently selected group
this.node.scrollTop = this.groupElem.offsetTop; this.node.scrollTop = this.groupElem.offsetTop;
} }
/**
* Set focus on active or first slot element, if applicable.
*/
if (this.slotItems[this.activeSlotId]) {
this.slotItems[this.activeSlotId].focus();
} else if (this.slotItems[this.firstSlotId]) {
this.slotItems[this.firstSlotId].focus();
}
}
/**
* Handle focus if the component updates
*
*/
componentWillUnmount() {
if (this.props.slotDiv) {
this.props.slotDiv.focus();
}
} }
/** /**
@@ -637,14 +401,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>
); );
} }
} }

View File

@@ -38,6 +38,7 @@ 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,

View File

@@ -1,6 +1,13 @@
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 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

View File

@@ -1,6 +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 Slider from '../components/Slider'; import Slider from '../components/Slider';
/** /**

View File

@@ -10,6 +10,7 @@ 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,

View File

@@ -13,6 +13,7 @@ import { ShoppingIcon } from '../components/SvgIcons';
* 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,
@@ -32,7 +33,7 @@ export default class CostSection extends TranslatedComponent {
this._buildRetrofitShip = this._buildRetrofitShip.bind(this); this._buildRetrofitShip = this._buildRetrofitShip.bind(this);
this._onBaseRetrofitChange = this._onBaseRetrofitChange.bind(this); this._onBaseRetrofitChange = this._onBaseRetrofitChange.bind(this);
this._defaultRetrofitName = this._defaultRetrofitName.bind(this); this._defaultRetrofitName = this._defaultRetrofitName.bind(this);
this._eddbShoppingList = this._inaraShoppingList.bind(this); this._eddbShoppingList = this._eddbShoppingList.bind(this);
let data = Ships[props.ship.id]; // Retrieve the basic ship properties, slots and defaults let data = Ships[props.ship.id]; // Retrieve the basic ship properties, slots and defaults
let retrofitName = this._defaultRetrofitName(props.ship.id, props.buildName); let retrofitName = this._defaultRetrofitName(props.ship.id, props.buildName);
@@ -306,8 +307,8 @@ export default class CostSection extends TranslatedComponent {
<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._sortCostBy.bind(this,'m')}>
{translate('module')} {translate('module')}
{shipDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} ${formats.pct(-1 * 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(-1 * 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._sortCostBy.bind(this, 'cr')} >{translate('credits')}</th>
</tr> </tr>
@@ -328,9 +329,9 @@ export default class CostSection extends TranslatedComponent {
} }
/** /**
* Open up a window for inara with a shopping list of our retrofit components * Open up a window for EDDB with a shopping list of our retrofit components
*/ */
_inaraShoppingList() { _eddbShoppingList() {
const { retrofitCosts } = this.state; const { retrofitCosts } = this.state;
const { ship } = this.props; const { ship } = this.props;
@@ -338,7 +339,7 @@ export default class CostSection extends TranslatedComponent {
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://inara.cz/inapi/corisearch.php?m=' + modIds.join(',')); window.open('https://eddb.io/station?m=' + modIds.join(','));
} }
/** /**
@@ -360,11 +361,11 @@ export default class CostSection extends TranslatedComponent {
for (let i = 0, l = retrofitCosts.length; i < l; i++) { for (let i = 0, l = retrofitCosts.length; i < l; i++) {
let item = retrofitCosts[i]; let item = retrofitCosts[i];
rows.push(<tr key={i} className={cn('highlight', { disabled: !item.retroItem.incCost })} onClick={this._toggleRetrofitCost.bind(this, item)}> 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='ptr' style={{ width: '1em' }}>{item.sellClassRating}</td>
<td className='le ptr shorten cap'>{translate(item.sellName)}</td> <td className='le ptr shorten cap'>{translate(item.sellName)}</td>
<td className='ptr' style={{ width: '1em' }}>{item.buyClassRating}</td> <td className='ptr' style={{ width: '1em' }}>{item.buyClassRating}</td>
<td className='le ptr shorten cap'>{translate(item.buyName)}</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> <td colSpan='2' className={cn('ri ptr', item.retroItem.incCost ? item.netCost > 0 ? 'warning' : 'secondary-disabled' : 'disabled')}>{int(item.netCost)}{units.CR}</td>
</tr>); </tr>);
} }
} else { } else {
@@ -387,7 +388,7 @@ export default class CostSection extends TranslatedComponent {
<tbody> <tbody>
{rows} {rows}
<tr className='ri'> <tr className='ri'>
<td className='lbl' ><button onClick={this._inaraShoppingList} 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 colSpan='3' 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}

View File

@@ -52,12 +52,12 @@ export default class Defence extends TranslatedComponent {
* @return {React.Component} contents * @return {React.Component} contents
*/ */
render() { render() {
const { opponent, sys, opponentWep } = this.props; const { ship, sys, opponentWep } = 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 { shield, armour, shielddamage, armourdamage } = this.state;
const pd = opponent.standard[4].m; const pd = ship.standard[4].m;
const shieldSourcesData = []; const shieldSourcesData = [];
const effectiveShieldData = []; const effectiveShieldData = [];
@@ -76,7 +76,6 @@ export default class Defence extends TranslatedComponent {
shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') }); shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') });
shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') }); shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') });
shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') }); shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') });
shieldSourcesData.push({ value: Math.round(shield.addition), label: translate('shield addition') });
if (shield.generator > 0) { if (shield.generator > 0) {
shieldSourcesTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>); shieldSourcesTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
@@ -102,19 +101,19 @@ export default class Defence extends TranslatedComponent {
// Add effective shield from resistances // Add effective shield from resistances
const rawMj = shield.generator + shield.boosters + shield.cells; const rawMj = shield.generator + shield.boosters + shield.cells;
const explosiveMj = rawMj / (shield.explosive.base) - rawMj; const explosiveMj = rawMj / (shield.explosive.generator * shield.explosive.boosters) - rawMj;
if (explosiveMj != 0) effectiveShieldExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(explosiveMj)}{units.MJ}</div>); if (explosiveMj != 0) effectiveShieldExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(explosiveMj)}{units.MJ}</div>);
const kineticMj = rawMj / (shield.kinetic.base) - rawMj; const kineticMj = rawMj / (shield.kinetic.generator * shield.kinetic.boosters) - rawMj;
if (kineticMj != 0) effectiveShieldKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(kineticMj)}{units.MJ}</div>); if (kineticMj != 0) effectiveShieldKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(kineticMj)}{units.MJ}</div>);
const thermalMj = rawMj / (shield.thermal.base) - rawMj; const thermalMj = rawMj / (shield.thermal.generator * shield.thermal.boosters) - rawMj;
if (thermalMj != 0) effectiveShieldThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(thermalMj)}{units.MJ}</div>); if (thermalMj != 0) effectiveShieldThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(thermalMj)}{units.MJ}</div>);
// Add effective shield from power distributor SYS pips // Add effective shield from power distributor SYS pips
if (shield.absolute.sys != 1) { if (shield.absolute.sys != 1) {
effectiveShieldAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.absolute.total - rawMj)}{units.MJ}</div>); effectiveShieldAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.absolute.sys - rawMj)}{units.MJ}</div>);
effectiveShieldExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.explosive.total - rawMj / shield.explosive.base)}{units.MJ}</div>); effectiveShieldExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.explosive.sys - rawMj)}{units.MJ}</div>);
effectiveShieldKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.kinetic.total - rawMj / shield.kinetic.base)}{units.MJ}</div>); effectiveShieldKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.kinetic.sys - rawMj)}{units.MJ}</div>);
effectiveShieldThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.thermal.total - rawMj / shield.thermal.base)}{units.MJ}</div>); effectiveShieldThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.thermal.sys - rawMj)}{units.MJ}</div>);
} }
} }
@@ -160,21 +159,18 @@ export default class Defence extends TranslatedComponent {
const effectiveArmourExplosiveTt = []; const effectiveArmourExplosiveTt = [];
const effectiveArmourKineticTt = []; const effectiveArmourKineticTt = [];
const effectiveArmourThermalTt = []; const effectiveArmourThermalTt = [];
const effectiveArmourCausticTt = [];
if (armour.bulkheads > 0) { if (armour.bulkheads > 0) {
armourSourcesTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>); armourSourcesTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourAbsoluteTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>); effectiveArmourAbsoluteTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>); effectiveArmourExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>); effectiveArmourKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>); effectiveArmourThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourCausticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
if (armour.reinforcement > 0) { if (armour.reinforcement > 0) {
armourSourcesTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>); armourSourcesTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourAbsoluteTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>); effectiveArmourAbsoluteTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>); effectiveArmourExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>); effectiveArmourKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>); effectiveArmourThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourCausticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
} }
} }
@@ -187,22 +183,17 @@ export default class Defence extends TranslatedComponent {
const armourDamageTakenExplosiveTt = []; const armourDamageTakenExplosiveTt = [];
armourDamageTakenExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}</div>); armourDamageTakenExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}</div>);
armourDamageTakenExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}</div>); armourDamageTakenExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}</div>);
if (armour.explosive.total != 1) effectiveArmourExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.explosive.total - rawArmour)}</div>); if (armour.explosive.bulkheads * armour.explosive.reinforcement != 1) effectiveArmourExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.explosive.bulkheads * armour.explosive.reinforcement) - rawArmour)}</div>);
const armourDamageTakenKineticTt = []; const armourDamageTakenKineticTt = [];
armourDamageTakenKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}</div>); armourDamageTakenKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}</div>);
armourDamageTakenKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}</div>); armourDamageTakenKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}</div>);
if (armour.kinetic.total != 1) effectiveArmourKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.kinetic.total - rawArmour)}</div>); if (armour.kinetic.bulkheads * armour.kinetic.reinforcement != 1) effectiveArmourKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.kinetic.bulkheads * armour.kinetic.reinforcement) - rawArmour)}</div>);
const armourDamageTakenThermalTt = []; const armourDamageTakenThermalTt = [];
armourDamageTakenThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}</div>); armourDamageTakenThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}</div>);
armourDamageTakenThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}</div>); armourDamageTakenThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}</div>);
if (armour.thermal.total != 1) effectiveArmourThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.thermal.total - rawArmour)}</div>); if (armour.thermal.bulkheads * armour.thermal.reinforcement != 1) effectiveArmourThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.thermal.bulkheads * armour.thermal.reinforcement) - rawArmour)}</div>);
const armourDamageTakenCausticTt = [];
armourDamageTakenCausticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.caustic.bulkheads)}</div>);
armourDamageTakenCausticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.caustic.reinforcement)}</div>);
if (armour.thermal.total != 1) effectiveArmourCausticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.caustic.total - rawArmour)}</div>);
const effectiveArmourData = []; const effectiveArmourData = [];
const effectiveAbsoluteArmour = armour.total / armour.absolute.total; const effectiveAbsoluteArmour = armour.total / armour.absolute.total;
@@ -213,39 +204,36 @@ export default class Defence extends TranslatedComponent {
effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic'), tooltip: effectiveArmourKineticTt }); effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic'), tooltip: effectiveArmourKineticTt });
const effectiveThermalArmour = armour.total / armour.thermal.total; const effectiveThermalArmour = armour.total / armour.thermal.total;
effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal'), tooltip: effectiveArmourThermalTt }); effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal'), tooltip: effectiveArmourThermalTt });
const effectiveCausticArmour = armour.total / armour.caustic.total;
effectiveArmourData.push({ value: Math.round(effectiveCausticArmour), label: translate('caustic'), tooltip: effectiveArmourCausticTt });
const armourDamageTakenData = []; const armourDamageTakenData = [];
armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute'), tooltip: armourDamageTakenTt }); armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute'), tooltip: armourDamageTakenTt });
armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive'), tooltip: armourDamageTakenExplosiveTt }); armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive'), tooltip: armourDamageTakenExplosiveTt });
armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic'), tooltip: armourDamageTakenKineticTt }); armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic'), tooltip: armourDamageTakenKineticTt });
armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal'), tooltip: armourDamageTakenThermalTt }); armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal'), tooltip: armourDamageTakenThermalTt });
armourDamageTakenData.push({ value: Math.round(armour.caustic.total * 100), label: translate('caustic'), tooltip: armourDamageTakenCausticTt });
return ( return (
<span id='defence'> <span id='defence'>
{shield.total ? <span> {shield.total ? <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(shield.total)}{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/>{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('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/>{shield.recover === Math.Inf ? translate('never') : formats.time(shield.recover)}</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/>{shield.recharge === Math.Inf ? translate('never') : formats.time(shield.recharge)}</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'>

View File

@@ -1,6 +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 Slider from '../components/Slider'; import Slider from '../components/Slider';
/** /**

View File

@@ -1,7 +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 { 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';
/** /**

View File

@@ -1,7 +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 { 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';
/** /**
@@ -52,7 +57,7 @@ export default class FSDProfile extends TranslatedComponent {
*/ */
_calcMaxRange(ship, fuel, mass) { _calcMaxRange(ship, fuel, mass) {
// Obtain the maximum range // Obtain the maximum range
return Calc.jumpRange(mass, ship.standard[2].m, Math.min(fuel, ship.standard[2].m.getMaxFuelPerJump()), ship); return Calc.jumpRange(mass, ship.standard[2].m, Math.min(fuel, ship.standard[2].m.getMaxFuelPerJump()));
} }
/** /**
@@ -64,7 +69,7 @@ export default class FSDProfile extends TranslatedComponent {
const { formats, translate, units } = language; const { formats, translate, units } = language;
const { ship, cargo, fuel } = this.props; const { ship, cargo, fuel } = this.props;
// Calculate bounds for our line chart - use thruster info for X // Calculate bounds for our line chart - use thruster info for X
const thrusters = ship.standard[1].m; const thrusters = ship.standard[1].m;
const fsd = ship.standard[2].m; const fsd = ship.standard[2].m;
@@ -72,7 +77,7 @@ export default class FSDProfile extends TranslatedComponent {
const maxMass = thrusters.getMaxMass(); const maxMass = thrusters.getMaxMass();
const mass = ship.unladenMass + fuel + cargo; const mass = ship.unladenMass + fuel + cargo;
const minRange = 0; const minRange = 0;
const maxRange = Calc.jumpRange(minMass + fsd.getMaxFuelPerJump(), fsd, fsd.getMaxFuelPerJump(), ship); const maxRange = Calc.jumpRange(minMass + fsd.getMaxFuelPerJump(), fsd, fsd.getMaxFuelPerJump());
// Add a mark at our current mass // Add a mark at our current mass
const mark = Math.min(mass, maxMass); const mark = Math.min(mass, maxMass);

View File

@@ -1,6 +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 Slider from '../components/Slider'; import Slider from '../components/Slider';
/** /**

View File

@@ -2,21 +2,12 @@ import React from 'react';
import cn from 'classnames'; import cn from 'classnames';
import Slot from './Slot'; import Slot from './Slot';
import Persist from '../stores/Persist'; import Persist from '../stores/Persist';
import { import { DamageAbsolute, DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications, Modified } from './SvgIcons';
DamageAbsolute,
DamageKinetic,
DamageThermal,
DamageExplosive,
MountFixed,
MountGimballed,
MountTurret,
ListModifications,
Modified
} from './SvgIcons';
import { Modifications } from 'coriolis-data/dist'; import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions'; import { stopCtxPropagation } from '../utils/UtilityFunctions';
import { blueprintTooltip } from '../utils/BlueprintFunctions'; import { blueprintTooltip } from '../utils/BlueprintFunctions';
/** /**
* Hardpoint / Utility Slot * Hardpoint / Utility Slot
*/ */
@@ -36,7 +27,7 @@ export default class HardpointSlot extends Slot {
* @return {string} Label * @return {string} Label
*/ */
_getMaxClassLabel(translate) { _getMaxClassLabel(translate) {
return translate(['U', 'S', 'M', 'L', 'H'][this.props.maxClass]); return translate(['U','S','M','L','H'][this.props.maxClass]);
} }
/** /**
@@ -75,78 +66,42 @@ export default class HardpointSlot extends Slot {
return <div className={className} draggable='true' onDragStart={drag} onDragEnd={drop}> return <div className={className} draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}> <div className={'cb'}>
<div className={'l'}> <div className={'l'}>
{m.mount && m.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} {m.mount && m.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed /></span> : ''}
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 == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} {m.mount && m.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : ''}
onMouseOut={tooltip.bind(null, null)}><MountGimballed/></span> : ''} {m.getDamageDist() && m.getDamageDist().K ? <span onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /></span> : ''}
{m.mount && m.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} {m.getDamageDist() && m.getDamageDist().T ? <span onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /></span> : ''}
onMouseOut={tooltip.bind(null, null)}><MountTurret/></span> : ''} {m.getDamageDist() && m.getDamageDist().E ? <span onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /></span> : ''}
{m.getDamageDist() && m.getDamageDist().K ? <span onMouseOver={termtip.bind(null, 'kinetic')} {m.getDamageDist() && m.getDamageDist().A ? <span onMouseOver={termtip.bind(null, 'absolute')} onMouseOut={tooltip.bind(null, null)}><DamageAbsolute /></span> : ''}
onMouseOut={tooltip.bind(null, null)}><DamageKinetic/></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 }
{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>
<div className={'r'}>{formats.round(m.getMass())}{u.T}</div> <div className={'r'}>{formats.round(m.getMass())}{u.T}</div>
</div> </div>
<div className={'cb'}> <div className={'cb'}>
{m.getDps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'dpssdps' : 'dps')} { m.getDps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'dpssdps' : 'dps')} onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}: {formats.round1(m.getDps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}: {formats.round1(m.getDps())} {m.getClip() ? { 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 }
<span>({formats.round1(m.getSDps())})</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.getDamage() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getDamage() ? 'shotdmg' : 'shotdmg')} { 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 }
onMouseOut={tooltip.bind(null, null)}>{translate('shotdmg')}: {formats.round1(m.getDamage())}</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.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'epsseps' : 'eps')} { m.getRange() ? <div className={'l'}>{translate('range', m.grp)} {formats.f1(m.getRange() / 1000)}{u.km}</div> : null }
onMouseOut={tooltip.bind(null, null)}>{translate('EPS')}: {formats.round1(m.getEps())}{u.MW} {m.getClip() ? { m.getScanTime() ? <div className={'l'}>{translate('scantime')} {formats.f1(m.getScanTime())}{u.s}</div> : null }
<span>({formats.round1(m.getEps() * m.getSustainedFactor())}{u.MW})</span> : null}</div> : null} { m.getFalloff() ? <div className={'l'}>{translate('falloff')} {formats.round(m.getFalloff() / 1000)}{u.km}</div> : null }
{m.getHps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'hpsshps' : 'hps')} { m.getShieldBoost() ? <div className={'l'}>+{formats.pct1(m.getShieldBoost())}</div> : null }
onMouseOut={tooltip.bind(null, null)}>{translate('HPS')}: {formats.round1(m.getHps())} {m.getClip() ? { m.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.int(m.getClip())}/{formats.int(m.getAmmo())}</div> : null }
<span>({formats.round1(m.getHps() * m.getSustainedFactor())})</span> : null}</div> : null} { m.getReload() ? <div className={'l'}>{translate('reload')}: {formats.round(m.getReload())}{u.s}</div> : null }
{m.getDps() && m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, 'dpe')} { m.getShotSpeed() ? <div className={'l'}>{translate('shotspeed')}: {formats.int(m.getShotSpeed())}{u.mps}</div> : null }
onMouseOut={tooltip.bind(null, null)}>{translate('DPE')}: {formats.f1(m.getDps() / m.getEps())}</div> : null} { m.getPiercing() ? <div className={'l'}>{translate('piercing')}: {formats.int(m.getPiercing())}</div> : null }
{m.getRoF() ? <div className={'l'} onMouseOver={termtip.bind(null, 'rof')} { m.getJitter() ? <div className={'l'}>{translate('jitter')}: {formats.f2(m.getJitter())}°</div> : null }
onMouseOut={tooltip.bind(null, null)}>{translate('ROF')}: {formats.f1(m.getRoF())}{u.ps}</div> : null} { showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
{m.getRange() ? <div { showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
className={'l'}>{translate('range', m.grp)} {formats.f1(m.getRange() / 1000)}{u.km}</div> : null} { showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
{m.getScanTime() ? <div { m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
className={'l'}>{translate('scantime')} {formats.f1(m.getScanTime())}{u.s}</div> : null} { m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
{m.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('wep_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}
{m.getScanAngle() ? <div className={'l'}>{translate('scan angle')}: {formats.f2(m.getScanAngle())}°</div> : null}
{m.getScanRange() ? <div className={'l'}>{translate('scan range')}: {formats.int(m.getScanRange())}{u.m}</div> : null}
{m.getMaxAngle() ? <div className={'l'}>{translate('max angle')}: {formats.f2(m.getMaxAngle())}°</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.getInfo() ? <div className='l'>{translate(m.getInfo())}</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>
</div>; </div>;
} else { } else {
return <div className={'empty'}>{translate('empty')}</div>; return <div className={'empty'}>{translate('empty')}</div>;
} }
} }
} }

View File

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

View File

@@ -9,13 +9,11 @@ import { Cogs, CoriolisLogo, Hammer, Help, Rocket, StatsBars } from './SvgIcons'
import { Ships } from 'coriolis-data/dist'; import { Ships } from 'coriolis-data/dist';
import Persist from '../stores/Persist'; import Persist from '../stores/Persist';
import { toDetailedExport } from '../shipyard/Serializer'; import { toDetailedExport } from '../shipyard/Serializer';
import Ship from '../shipyard/Ship';
import ModalDeleteAll from './ModalDeleteAll'; import ModalDeleteAll from './ModalDeleteAll';
import ModalExport from './ModalExport'; 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;
@@ -76,11 +74,8 @@ 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 = {
@@ -311,7 +306,7 @@ export default class Header extends TranslatedComponent {
_getShipsMenu() { _getShipsMenu() {
let shipList = []; let shipList = [];
for (let s of this.shipOrder) { for (let s in Ships) {
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>);
} }
@@ -377,32 +372,6 @@ 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) {
if (announce.expiry < Date.now()) {
continue;
}
announcements.push(<Announcement text={announce.text} />);
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
@@ -525,15 +494,6 @@ 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,10 +504,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={this.update}>{translate('PHRASE_UPDATE_RDY')}</div>} {this.props.appCacheUpdate && <div id="app-update" onClick={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>}
{this.props.appCacheUpdate ? <a className={'view-changes'} href={'https://github.com/EDCD/coriolis/compare/edcd:develop@{' + window.CORIOLIS_DATE + '}...edcd:develop'} target="_blank">
{'View Release Changes'}
</a> : null}
<Link className='l' href='/' style={{ marginRight: '1em' }} title='Home'><CoriolisLogo className='icon xl' /></Link> <Link className='l' href='/' style={{ marginRight: '1em' }} title='Home'><CoriolisLogo className='icon xl' /></Link>
<div className='l menu'> <div className='l menu'>
@@ -571,23 +528,6 @@ 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 ?
<div className='l menu'>
<a href="https://youtu.be/4SvnLcefhtI" target="_blank">
<div className={cn('menu-header')}>
<Rocket className='warning'/><span className='menu-item-label'>{translate('please migrate to coriolis.io')}</span>
</div>
</a>
</div> : null
}
<div className='r menu'> <div className='r menu'>
<div className={cn('menu-header', { selected: openedMenu == 'settings' })} onClick={this._openSettings}> <div className={cn('menu-header', { selected: openedMenu == 'settings' })} onClick={this._openSettings}>
<Cogs className='xl warning'/><span className='menu-item-label'>{translate('settings')}</span> <Cogs className='xl warning'/><span className='menu-item-label'>{translate('settings')}</span>

View File

@@ -33,9 +33,6 @@ export default class InternalSlot extends Slot {
let modTT = translate('modified'); let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) { if (m && m.blueprint && m.blueprint.name) {
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade; modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
if (m.blueprint.special && m.blueprint.special.id >= 0) {
modTT += ', ' + translate(m.blueprint.special.name);
}
modTT = ( modTT = (
<div> <div>
<div>{modTT}</div> <div>{modTT}</div>
@@ -63,9 +60,6 @@ export default class InternalSlot extends Slot {
{ m.getSpinup() ? <div className={'l'}>{translate('spinup')}: {formats.f1(m.getSpinup())}{u.s}</div> : null } { m.getSpinup() ? <div className={'l'}>{translate('spinup')}: {formats.f1(m.getSpinup())}{u.s}</div> : null }
{ m.getDuration() ? <div className={'l'}>{translate('duration')}: {formats.f1(m.getDuration())}{u.s}</div> : null } { m.getDuration() ? <div className={'l'}>{translate('duration')}: {formats.f1(m.getDuration())}{u.s}</div> : null }
{ m.grp === 'scb' ? <div className={'l'}>{translate('cells')}: {formats.int(m.getAmmo() + 1)}</div> : null } { m.grp === 'scb' ? <div className={'l'}>{translate('cells')}: {formats.int(m.getAmmo() + 1)}</div> : null }
{ m.grp === 'gsrp' ? <div className={'l'}>{translate('shield addition')}: {formats.f1(m.getShieldAddition())}{u.MJ}</div> : null }
{ m.grp === 'gfsb' ? <div className={'l'}>{translate('jump addition')}: {formats.f1(m.getJumpBoost())}{u.LY}</div> : null }
{ m.grp === 'gs' ? <div className={'l'}>{translate('shield addition')}: {formats.f1(m.getShieldAddition())}{u.MJ}</div> : null }
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('shieldreinforcement')}: {formats.f1(m.getDuration() * m.getShieldReinforcement())}{u.MJ}</div> : null } { m.getShieldReinforcement() ? <div className={'l'}>{translate('shieldreinforcement')}: {formats.f1(m.getDuration() * m.getShieldReinforcement())}{u.MJ}</div> : null }
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('total')}: {formats.int((m.getAmmo() + 1) * (m.getDuration() * m.getShieldReinforcement()))}{u.MJ}</div> : null } { m.getShieldReinforcement() ? <div className={'l'}>{translate('total')}: {formats.int((m.getAmmo() + 1) * (m.getDuration() * m.getShieldReinforcement()))}{u.MJ}</div> : null }
{ m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null } { m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null }
@@ -73,23 +67,21 @@ export default class InternalSlot extends Slot {
{ m.getRange() ? <div className={'l'}>{translate('range')} {formats.f2(m.getRange())}{u.km}</div> : null } { m.getRange() ? <div className={'l'}>{translate('range')} {formats.f2(m.getRange())}{u.km}</div> : null }
{ m.getRangeT() ? <div className={'l'}>{translate('ranget')} {formats.f1(m.getRangeT())}{u.s}</div> : null } { m.getRangeT() ? <div className={'l'}>{translate('ranget')} {formats.f1(m.getRangeT())}{u.s}</div> : null }
{ m.getTime() ? <div className={'l'}>{translate('time')}: {formats.time(m.getTime())}</div> : null } { m.getTime() ? <div className={'l'}>{translate('time')}: {formats.time(m.getTime())}</div> : null }
{ m.getHackTime() ? <div className={'l'}>{translate('hacktime')}: {formats.time(m.getHackTime())}</div> : null }
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null } { m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
{ m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null } { m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
{ m.rangeLS === null ? <div className={'l'}>{u.Ls}</div> : null } { m.rangeLS === null ? <div className={'l'}>{u.Ls}</div> : null }
{ m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</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.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.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 } { 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.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null } { showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null } { showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
{ showModuleResistances && m.getCausticResistance() ? <div className='l'>{translate('causres')}: {formats.pct(m.getCausticResistance())}</div> : null }
{ m.getHullReinforcement() ? <div className='l'>{translate('armour')}: {formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)}</div> : null } { m.getHullReinforcement() ? <div className='l'>{translate('armour')}: {formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)}</div> : null }
{ m.getProtection() ? <div className='l'>{translate('protection')}: {formats.rPct(m.getProtection())}</div> : null } { m.getProtection() ? <div className='l'>{translate('protection')}: {formats.rPct(m.getProtection())}</div> : null }
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null } { m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
{ m.getInfo() ? <div className='l'>{translate(m.getInfo())}</div> : null } { m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
{ m && validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
</div> </div>
</div>; </div>;
} else { } else {

View File

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

View File

@@ -1,8 +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 { 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';
/** /**
@@ -57,7 +61,7 @@ export default class JumpRange extends TranslatedComponent {
const fuel = this.state.fuelLevel * ship.fuelCapacity; const fuel = this.state.fuelLevel * ship.fuelCapacity;
// Obtain the jump range // Obtain the jump range
return Calc.jumpRange(ship.unladenMass + fuel + cargo, fsd, fuel, ship); return Calc.jumpRange(ship.unladenMass + fuel + cargo, fsd, fuel);
} }
/** /**

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ContainerDimensions from 'react-container-dimensions'; import Measure from 'react-measure';
import * as d3 from 'd3'; import * as d3 from 'd3';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
@@ -67,17 +67,21 @@ export default class LineChart extends TranslatedComponent {
xAxisScale, xAxisScale,
yScale, yScale,
tipHeight: 2 + (1.2 * (series ? series.length : 0.8)), tipHeight: 2 + (1.2 * (series ? series.length : 0.8)),
dimensions: {
width: 100,
height: 100
}
}; };
} }
/** /**
* Update tooltip content * Update tooltip content
* @param {number} xPos x coordinate * @param {number} xPos x coordinate
* @param {number} width current container width
*/ */
_tooltip(xPos, width) { _tooltip(xPos) {
let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props; let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props;
let { xScale, yScale } = this.state; let { xScale, yScale } = this.state;
let { width } = this.state.dimensions;
let { formats, translate } = this.context.language; let { formats, translate } = this.context.language;
let x0 = xScale.invert(xPos), let x0 = xScale.invert(xPos),
y0 = func(x0), y0 = func(x0),
@@ -116,11 +120,11 @@ export default class LineChart extends TranslatedComponent {
* Update dimensions based on properties and scale * Update dimensions based on properties and scale
* @param {Object} props React Component properties * @param {Object} props React Component properties
* @param {number} scale size ratio / scale * @param {number} scale size ratio / scale
* @param {number} width current width of the container
* @returns {Object} calculated dimensions * @returns {Object} calculated dimensions
*/ */
_updateDimensions(props, scale, width) { _updateDimensions(props, scale) {
const { xMax, xMin, yMin, yMax } = props; const { xMax, xMin, yMin, yMax } = props;
const { width, height } = this.state.dimensions;
const innerWidth = width - MARGIN.left - MARGIN.right; const innerWidth = width - MARGIN.left - MARGIN.right;
const outerHeight = Math.round(width * props.aspect); const outerHeight = Math.round(width * props.aspect);
const innerHeight = outerHeight - MARGIN.top - MARGIN.bottom; const innerHeight = outerHeight - MARGIN.top - MARGIN.bottom;
@@ -145,11 +149,10 @@ export default class LineChart extends TranslatedComponent {
/** /**
* Move and update tooltip * Move and update tooltip
* @param {SyntheticEvent} e Event * @param {SyntheticEvent} e Event
* @param {number} width current container width
*/ */
_moveTip(e, width) { _moveTip(e) {
let clientX = e.touches ? e.touches[0].clientX : e.clientX; let clientX = e.touches ? e.touches[0].clientX : e.clientX;
this._tooltip(Math.round(clientX - e.currentTarget.getBoundingClientRect().left), width); this._tooltip(Math.round(clientX - e.currentTarget.getBoundingClientRect().left));
} }
/** /**
@@ -224,58 +227,57 @@ export default class LineChart extends TranslatedComponent {
* @return {React.Component} Chart SVG * @return {React.Component} Chart SVG
*/ */
render() { render() {
return ( const { innerWidth, outerHeight, innerHeight } = this._updateDimensions(this.props, this.context.sizeRatio);
<ContainerDimensions> const { width, height } = this.state.dimensions;
{ ({ width, height }) => { const { xMin, xMax, xLabel, yLabel, xUnit, yUnit, xMark, colors } = this.props;
const { innerWidth, outerHeight, innerHeight } = this._updateDimensions(this.props, this.context.sizeRatio, width, height); const { tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state;
const { xMin, xMax, xLabel, yLabel, xUnit, yUnit, xMark, colors } = this.props; const line = this.line;
const { tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state; const lines = seriesLines.map((line, i) => <path key={i} className='line' fill='none' stroke={colors[i]} strokeWidth='1' d={line(seriesData)} />).reverse();
const lines = seriesLines.map((line, i) => <path key={i} className='line' fill='none' stroke={colors[i]} strokeWidth='1' d={line(seriesData)} />).reverse();
const markX = xMark ? innerWidth * (xMark - xMin) / (xMax - xMin) : 0; const markX = xMark ? innerWidth * (xMark - xMin) / (xMax - xMin) : 0;
const xmark = xMark ? <path key={'mark'} className='line' fill='none' strokeDasharray='5,5' stroke={'#ff8c0d'} strokeWidth='1' d={'M ' + markX + ' ' + innerHeight + ' L ' + markX + ' 0'} /> : ''; const xmark = xMark ? <path key={'mark'} className='line' fill='none' strokeDasharray='5,5' stroke={'#ff8c0d'} strokeWidth='1' d={'M ' + markX + ' ' + innerHeight + ' L ' + markX + ' 0'} /> : '';
return (
<div width={width} height={height}> return (
<svg style={{ width: '100%', height: outerHeight }}> <Measure width='100%' whitelist={['width', 'top']} onMeasure={ (dimensions) => { this.setState({ dimensions }); }}>
<g transform={`translate(${MARGIN.left},${MARGIN.top})`}> <div width={width} height={height}>
<g>{xmark}</g> <svg style={{ width: '100%', height: outerHeight }}>
<g>{lines}</g> <g transform={`translate(${MARGIN.left},${MARGIN.top})`}>
<g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}> <g>{xmark}</g>
<text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}> <g>{lines}</g>
<tspan>{xLabel}</tspan> <g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}>
<tspan className='metric'> ({xUnit})</tspan> <text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}>
</text> <tspan>{xLabel}</tspan>
</g> <tspan className='metric'> ({xUnit})</tspan>
<g className='y axis' ref={(elem) => d3.select(elem).call(this.yAxis)}> </text>
<text className='cap' transform='rotate(-90)' y='-50' dy='.1em' x={innerHeight / -2} style={{ textAnchor: 'middle' }}> </g>
<tspan>{yLabel}</tspan> <g className='y axis' ref={(elem) => d3.select(elem).call(this.yAxis)}>
{ yUnit && <tspan className='metric'> ({yUnit})</tspan> } <text className='cap' transform='rotate(-90)' y='-50' dy='.1em' x={innerHeight / -2} style={{ textAnchor: 'middle' }}>
</text> <tspan>{yLabel}</tspan>
</g> { yUnit && <tspan className='metric'> ({yUnit})</tspan> }
<g ref={(g) => this.tipContainer = d3.select(g)} style={{ display: 'none' }}> </text>
<rect className='tooltip' height={tipHeight + 'em'}></rect> </g>
{detailElems} <g ref={(g) => this.tipContainer = d3.select(g)} style={{ display: 'none' }}>
</g> <rect className='tooltip' height={tipHeight + 'em'}></rect>
<g ref={(g) => this.markersContainer = d3.select(g)} style={{ display: 'none' }}> {detailElems}
{markerElems} </g>
</g> <g ref={(g) => this.markersContainer = d3.select(g)} style={{ display: 'none' }}>
<rect {markerElems}
fillOpacity='0' </g>
height={innerHeight} <rect
width={innerWidth + 1} fillOpacity='0'
onMouseEnter={this._showTip} height={innerHeight}
onTouchStart={this._showTip} width={innerWidth + 1}
onMouseLeave={this._hideTip} onMouseEnter={this._showTip}
onTouchEnd={this._hideTip} onTouchStart={this._showTip}
onMouseMove={e => this._moveTip(e, width)} onMouseLeave={this._hideTip}
onTouchMove={e => this._moveTip(e, width)} onTouchEnd={this._hideTip}
/> onMouseMove={this._moveTip}
</g> onTouchMove={this._moveTip}
</svg> />
</div> </g>
); </svg>
}} </div>
</ContainerDimensions> </Measure>
); );
} }
} }

View File

@@ -6,6 +6,7 @@ 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
*/ */

View File

@@ -11,10 +11,7 @@ import * as ModuleUtils from '../shipyard/ModuleUtils';
import { fromDetailedBuild } from '../shipyard/Serializer'; import { fromDetailedBuild } from '../shipyard/Serializer';
import { Download } from './SvgIcons'; import { Download } from './SvgIcons';
import { outfitURL } from '../utils/UrlGenerators'; import { outfitURL } from '../utils/UrlGenerators';
import { shipFromJson, shipModelFromJson } from '../utils/CompanionApiUtils'; import * as CompanionApiUtils from '../utils/CompanionApiUtils';
import { shipFromLoadoutJSON } from '../utils/JournalUtils';
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\\- ]+)');
@@ -89,7 +86,6 @@ export default class ModalImport extends TranslatedComponent {
static propTypes = { static propTypes = {
importString: PropTypes.string, // Optional: Default data for import modal
builds: PropTypes.object, // Optional: Import object builds: PropTypes.object, // Optional: Import object
}; };
@@ -103,12 +99,11 @@ 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,
errorMsg: null, errorMsg: null,
importString: props.importString || null, importString: null,
importValid: false, importValid: false,
insurance: null insurance: null
}; };
@@ -116,28 +111,12 @@ 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
@@ -147,11 +126,7 @@ export default class ModalImport extends TranslatedComponent {
if (importData.builds && typeof importData.builds == 'object') { if (importData.builds && typeof importData.builds == 'object') {
for (let shipId in importData.builds) { for (let shipId in importData.builds) {
for (let buildName in importData.builds[shipId]) { for (let buildName in importData.builds[shipId]) {
try { validateBuild(shipId, importData.builds[shipId][buildName], buildName);
validateBuild(shipId, importData.builds[shipId][buildName], buildName);
} catch (err) {
delete importData.builds[shipId][buildName];
}
} }
} }
this.setState({ builds: importData.builds }); this.setState({ builds: importData.builds });
@@ -180,7 +155,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({ moduleDiscount: importData.moduleDiscount * 1 }); this.setState({ shipDiscount: importData.moduleDiscount * 1 });
} }
if (typeof importData.insurance == 'string') { if (typeof importData.insurance == 'string') {
@@ -216,8 +191,8 @@ export default class ModalImport extends TranslatedComponent {
* @throws {string} if parse/import fails * @throws {string} if parse/import fails
*/ */
_importCompanionApiBuild(build) { _importCompanionApiBuild(build) {
const shipModel = shipModelFromJson(build); const shipModel = CompanionApiUtils.shipModelFromJson(build);
const ship = shipFromJson(build); const ship = CompanionApiUtils.shipFromJson(build);
let builds = {}; let builds = {};
builds[shipModel] = {}; builds[shipModel] = {};
@@ -323,30 +298,6 @@ export default class ModalImport extends TranslatedComponent {
this.setState({ builds, singleBuild: true }); this.setState({ builds, singleBuild: true });
} }
/**
* Import SLEF formatted builds. Sets state to a map of the builds on success
* and flags if there was only a single build.
*
* @param {string} importData - Array of the list of builds.
* @throws {string} If parse / import fails
*/
_importSlefBuilds(importData) {
const builds = importData.reduce((memo, { data }) => {
const shipModel = shipModelFromJson(data);
const ship = shipFromLoadoutJSON(data);
const shipTemplate = Ships[shipModel];
const shipName = data.ShipName || shipTemplate.properties.name;
const key = `Imported ${shipName}`;
memo[shipModel] = {};
memo[shipModel][key] = ship.toString();
return memo;
}, {});
this.setState({ builds, singleBuild: Object.keys(builds).length === 1 });
}
/** /**
* Validate the import string / text box contents * Validate the import string / text box contents
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
@@ -381,10 +332,8 @@ export default class ModalImport extends TranslatedComponent {
throw 'Must be an object or array!'; throw 'Must be an object or array!';
} }
if (importData?.[0]?.header?.appName) { // has SLEF envelope? if (importData.modules != null && importData.modules.Armour != null) { // Only the companion API has this information
this._importSlefBuilds(importData); this._importCompanionApiBuild(importData); // Single sihp definition
} else if (importData.modules != null && importData.modules.Armour != null) { // Only the companion API has this information
this._importCompanionApiBuild(importData); // Single ship definition
} else if (importData.ship != null && importData.ship.modules != null && importData.ship.modules.Armour != null) { // Only the companion API has this information } else if (importData.ship != null && importData.ship.modules != null && importData.ship.modules.Armour != null) { // Only the companion API has this information
this._importCompanionApiBuild(importData.ship); // Complete API dump this._importCompanionApiBuild(importData.ship); // Complete API dump
} else if (importData instanceof Array) { // Must be detailed export json } else if (importData instanceof Array) { // Must be detailed export json
@@ -392,14 +341,12 @@ 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); // console.log(e.stack);
this.setState({ errorMsg: (typeof e == 'string') ? e : 'Cannot Parse the data!' }); this.setState({ errorMsg: (typeof e == 'string') ? e : 'Cannot Parse the data!' });
return; return;
} }
@@ -413,10 +360,6 @@ 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;
@@ -533,7 +476,7 @@ export default class ModalImport extends TranslatedComponent {
if (!state.processed) { if (!state.processed) {
importStage = ( importStage = (
<div> <div>
<textarea spellCheck={false} className='cb json' ref={node => this.importField = node} onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} /> <textarea 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>
@@ -570,7 +513,7 @@ export default class ModalImport extends TranslatedComponent {
{comparisonRows} {comparisonRows}
</tbody> </tbody>
</table> </table>
); );
} }
if(this.state.canEdit) { if(this.state.canEdit) {

View File

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

View File

@@ -1,336 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import request from 'superagent';
import Persist from '../stores/Persist';
const zlib = require('zlib');
const base64url = require('base64url');
/**
* Permalink modal
*/
export default class ModalShoppingList extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this.state = {
matsList: '',
mats: {},
failed: false,
cmdrName: Persist.getCmdr().selected,
cmdrs: Persist.getCmdr().cmdrs,
matsPerGrade: Persist.getRolls(),
blueprints: []
};
}
/**
* React component did mount
*/
componentDidMount() {
this.renderMats();
if (this.checkBrowserIsCompatible()) {
this.getCommanders();
this.registerBPs();
}
}
/**
* Find all blueprints needed to make a build.
*/
registerBPs() {
const ship = this.props.ship;
let blueprints = [];
for (const module of ship.costList) {
if (module.type === 'SHIP') {
continue;
}
if (module.m && module.m.blueprint) {
if (!module.m.blueprint.grade || !module.m.blueprint.grades) {
continue;
}
if (module.m.blueprint.special) {
blueprints.push({ uuid: module.m.blueprint.special.uuid, number: 1 });
}
for (const g in module.m.blueprint.grades) {
if (!module.m.blueprint.grades.hasOwnProperty(g)) {
continue;
}
if (g > module.m.blueprint.grade) {
continue;
}
blueprints.push({ uuid: module.m.blueprint.grades[g].uuid, number: this.state.matsPerGrade[g] });
}
}
}
this.setState({ blueprints });
}
/**
* Check browser isn't firefox.
* @return {boolean} true if compatible, false if not.
*/
checkBrowserIsCompatible() {
// Firefox 1.0+
return typeof InstallTrigger === 'undefined';
}
/**
* Get a list of commanders from EDEngineer.
*/
getCommanders() {
request
.get('http://localhost:44405/commanders')
.end((err, res) => {
if (err) {
console.log(err);
return this.setState({ failed: true });
}
const cmdrs = JSON.parse(res.text);
if (!this.state.cmdrName) {
this.setState({ cmdrName: cmdrs[0] });
}
this.setState({ cmdrs }, () => {
Persist.setCmdr({ selected: this.state.cmdrName, cmdrs });
});
});
}
/**
* Send all blueprints to ED Engineer
* @param {Event} event React event
*/
sendToEDEng(event) {
event.preventDefault();
let translate = this.context.language.translate;
const target = event.target;
target.disabled = this.state.blueprints.length > 0;
if (this.state.blueprints.length === 0) {
target.innerText = translate('No modded components.');
target.disabled = true;
setTimeout(() => {
target.innerText = translate('Send to EDEngineer');
target.disabled = false;
}, 3000);
} else {
target.innerText = translate('Sending...');
}
let countSent = 0;
let countTotal = this.state.blueprints.length;
for (const i of this.state.blueprints) {
request
.patch(`http://localhost:44405/${this.state.cmdrName}/shopping-list`)
.field('uuid', i.uuid)
.field('size', i.number)
.end(err => {
if (err) {
console.log(err);
if (err.message !== 'Bad Request') {
this.setState({ failed: true });
}
}
countSent++;
if (countSent === countTotal) {
target.disabled = false;
target.innerText = translate('Send to EDEngineer');
}
});
}
}
/**
* Send all blueprints to EDOMH. This is a modified copy of registerBPs because this.state.blueprints was empty when I tried to modify sendToEDEng and I couldn't figure out why
* @param {Event} event React event
*/
sendToEDOMH(event) {
event.preventDefault();
const ship = this.props.ship;
let blueprints = [];
//create the json
for (const module of ship.costList) {
if (module.type === 'SHIP') {
continue;
}
if (module.m && module.m.blueprint) {
if (!module.m.blueprint.grade || !module.m.blueprint.grades) {
continue;
}
if (module.m.blueprint.special) {
blueprints.push({
"item": module.m.symbol,
"blueprint": module.m.blueprint.special.edname
});
}
for (const g in module.m.blueprint.grades) {
if (!module.m.blueprint.grades.hasOwnProperty(g)) {
continue;
}
if (g < module.m.blueprint.grade) {
continue;
}
blueprints.push({
"item": module.m.symbol,
"blueprint": module.m.blueprint.fdname,
"grade": module.m.blueprint.grade,
"highestGradePercentage":1.0
});
}
}
}
//create JSON to encode
let baseJson = {
"version":1,
"name":ship.name, // TO-DO: Import build name and put that here correctly
"items": blueprints
}
let JSONString = JSON.stringify(baseJson)
let deflated = zlib.deflateSync(JSONString)
//actually encode
let link = base64url.encode(deflated)
link = "edomh://coriolis/?" + link;
window.open(link, "_self")
}
/**
* Convert mats object to string
*/
renderMats() {
const ship = this.props.ship;
let mats = {};
for (const module of ship.costList) {
if (module.type === 'SHIP') {
continue;
}
if (module.m && module.m.blueprint) {
if (!module.m.blueprint.grade || !module.m.blueprint.grades) {
continue;
}
for (const g in module.m.blueprint.grades) {
if (!module.m.blueprint.grades.hasOwnProperty(g)) {
continue;
}
if (g > module.m.blueprint.grade) {
continue;
}
for (const i in module.m.blueprint.grades[g].components) {
if (!module.m.blueprint.grades[g].components.hasOwnProperty(i)) {
continue;
}
if (mats[i]) {
mats[i] += module.m.blueprint.grades[g].components[i] * this.state.matsPerGrade[g];
} else {
mats[i] = module.m.blueprint.grades[g].components[i] * this.state.matsPerGrade[g];
}
}
if (module.m.blueprint.special) {
for (const j in module.m.blueprint.special.components) {
if (!module.m.blueprint.special.components.hasOwnProperty(j)) {
continue;
}
if (mats[j]) {
mats[j] += module.m.blueprint.special.components[j];
} else {
mats[j] = module.m.blueprint.special.components[j];
}
}
}
}
}
}
let matsString = '';
for (const i in mats) {
if (!mats.hasOwnProperty(i)) {
continue;
}
if (mats[i] === 0) {
delete mats[i];
continue;
}
matsString += `${i}: ${mats[i]}\n`;
}
this.setState({ matsList: matsString, mats });
}
/**
* Handler for changing roll amounts
* @param {SyntheticEvent} e React Event
*/
changeHandler(e) {
let grade = e.target.id;
let newState = this.state.matsPerGrade;
newState[grade] = parseInt(e.target.value);
this.setState({ matsPerGrade: newState });
Persist.setRolls(newState);
this.renderMats();
this.registerBPs();
}
/**
* Handler for changing cmdr name
* @param {SyntheticEvent} e React Event
*/
cmdrChangeHandler(e) {
let cmdrName = e.target.value;
this.setState({ cmdrName }, () => {
Persist.setCmdr({ selected: this.state.cmdrName, cmdrs: this.state.cmdrs });
});
}
/**
* Render the modal
* @return {React.Component} Modal Content
*/
render() {
let translate = this.context.language.translate;
this.changeHandler = this.changeHandler.bind(this);
const compatible = this.checkBrowserIsCompatible();
this.cmdrChangeHandler = this.cmdrChangeHandler.bind(this);
this.sendToEDEng = this.sendToEDEng.bind(this);
this.sendToEDOMH = this.sendToEDOMH.bind(this);
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2>{translate('PHRASE_SHOPPING_MATS')}</h2>
<label>{translate('Grade 1 rolls ')}</label>
<input id={1} type={'number'} min={0} defaultValue={this.state.matsPerGrade[1]} onChange={this.changeHandler} />
<br/>
<label>{translate('Grade 2 rolls ')}</label>
<input id={2} type={'number'} min={0} defaultValue={this.state.matsPerGrade[2]} onChange={this.changeHandler} />
<br/>
<label>{translate('Grade 3 rolls ')}</label>
<input id={3} type={'number'} min={0} value={this.state.matsPerGrade[3]} onChange={this.changeHandler} />
<br/>
<label>{translate('Grade 4 rolls ')}</label>
<input id={4} type={'number'} min={0} value={this.state.matsPerGrade[4]} onChange={this.changeHandler} />
<br/>
<label>{translate('Grade 5 rolls ')}</label>
<input id={5} type={'number'} min={0} value={this.state.matsPerGrade[5]} onChange={this.changeHandler} />
<div>
<textarea className='cb json' readOnly value={this.state.matsList} />
</div>
<label hidden={!compatible} className={'l cap'}>{translate('CMDR Name')}</label>
<br/>
<select hidden={!compatible} className={'cmdr-select l cap'} onChange={this.cmdrChangeHandler} defaultValue={this.state.cmdrName}>
{this.state.cmdrs.map(e => <option key={e}>{e}</option>)}
</select>
<br/>
<p hidden={!this.state.failed} id={'failed'} className={'l'}>{translate('PHRASE_FAIL_EDENGINEER')}</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 style={{marginTop: 5}} className={'l cb dismiss cap'} disabled={!!this.state.failed} onClick={this.sendToEDOMH}>{translate('Send to EDOMH')}</button>
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
</div>;
}
}

View File

@@ -3,22 +3,18 @@ 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 { isChangeValueBeneficial } from '../utils/BlueprintFunctions';
import { Modifications } from 'coriolis-data/dist';
/** /**
* Modification * Modification
*/ */
export default class Modification extends TranslatedComponent { export default class Modification extends TranslatedComponent {
static propTypes = { static propTypes = {
ship: PropTypes.object.isRequired, ship: PropTypes.object.isRequired,
m: PropTypes.object.isRequired, m: PropTypes.object.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
value: PropTypes.number.isRequired, value: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired
onKeyDown: PropTypes.func.isRequired,
modItems: PropTypes.array.isRequired,
handleModChange: PropTypes.func.isRequired
}; };
/** /**
@@ -39,37 +35,31 @@ export default class Modification extends TranslatedComponent {
* in a value by hand * in a value by hand
*/ */
_updateValue(value) { _updateValue(value) {
this.setState({ value }); const name = this.props.name;
let reCast = String(Number(value));
if (reCast.endsWith(value) || reCast.startsWith(value)) {
let { m, name, ship } = this.props;
value = Math.max(Math.min(value, 50000), -50000);
ship.setModification(m, name, value, true);
}
}
/** let scaledValue = Math.round(Number(value) * 100);
* Triggered when a key is pressed down with focus on the number editor. // Limit to +1000% / -99.99%
* @param {SyntheticEvent} event Key down event if (scaledValue > 100000) {
*/ scaledValue = 100000;
_keyDown(event) { value = 1000;
if (event.key == 'Enter') {
this._updateFinished();
} }
this.props.onKeyDown(event); if (scaledValue < -9999) {
scaledValue = -9999;
value = -99.99;
}
let m = this.props.m;
let ship = this.props.ship;
ship.setModification(m, name, scaledValue, true);
this.setState({ value });
} }
/** /**
* Triggered when an update to slider value is finished i.e. when losing focus * Triggered when an update to slider value is finished i.e. when losing focus
*
* pnellesen (24/05/2018): added value check below - this should prevent experimental effects from being recalculated
* with each onBlur event, even when no change has actually been made to the field.
*/ */
_updateFinished() { _updateFinished() {
if (this.props.value != this.state.value) { this.props.onChange();
this.props.handleModChange(true);
this.props.onChange();
}
} }
/** /**
@@ -77,56 +67,28 @@ export default class Modification extends TranslatedComponent {
* @return {React.Component} modification * @return {React.Component} modification
*/ */
render() { render() {
let { translate, formats, units } = this.context.language; let translate = this.context.language.translate;
let { m, name } = this.props; let { m, name } = this.props;
let modValue = m.getChange(name);
let isOverwrite = Modifications.modifications[name].method === 'overwrite';
if (name === 'damagedist') { if (name === 'damagedist') {
// We don't show damage distribution // We don't show damage distribution
return null; return null;
} }
let inputClassNames = { let symbol;
'cb': true, if (name === 'jitter') {
'greyed-out': !this.props.highlight symbol = '°';
}; } else if (name !== 'burst' && name != 'burstrof') {
symbol = '%';
}
if (symbol) {
symbol = ' (' + symbol + ')';
}
return ( return (
<div onBlur={this._updateFinished.bind(this)} key={name} <div onBlur={this._updateFinished.bind(this)} className={'cb'} key={name}>
className={cn('cb', 'modification-container')} <div className={'cb'}>{translate(name, m.grp)}{symbol}</div>
ref={ modItem => this.props.modItems[name] = modItem }> <NumberEditor className={'cb'} style={{ width: '90%', textAlign: 'center' }} step={0.01} stepModifier={1} decimals={2} value={this.state.value} onValueChange={this._updateValue.bind(this)} />
<span className={'cb'}>{translate(name, m.grp)}</span>
<span className={'header-adjuster'}></span>
<table style={{ width: '100%' }}>
<tbody>
<tr>
<td className={'input-container'}>
<span>
{this.props.editable ?
<NumberEditor className={cn(inputClassNames)} value={this.state.value}
decimals={2} style={{ textAlign: 'right' }} step={0.01}
stepModifier={1} onKeyDown={this._keyDown.bind(this)}
onValueChange={this._updateValue.bind(this)} /> :
<input type="text" value={formats.f2(this.state.value)}
disabled className={cn('number-editor', 'greyed-out')}
style={{ textAlign: 'right', cursor: 'inherit' }}/>
}
<span className={'unit-container'}>
{units[m.getStoredUnitFor(name)]}
</span>
</span>
</td>
<td style={{ textAlign: 'center' }} className={
modValue ?
isChangeValueBeneficial(name, modValue) ? 'secondary' : 'warning' :
''
}>
{formats.f2(modValue / 100) || 0}{isOverwrite ? '' : '%'}
</td>
</tr>
</tbody>
</table>
</div> </div>
); );
} }

View File

@@ -2,33 +2,22 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as _ from 'lodash'; import * as _ from 'lodash';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import { stopCtxPropagation } from '../utils/UtilityFunctions'; import { isEmpty, stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames'; import cn from 'classnames';
import { Modifications } from 'coriolis-data/dist'; import { Modifications } from 'coriolis-data/dist';
import Modification from './Modification'; import Modification from './Modification';
import { import { getBlueprint, blueprintTooltip, setWorst, setBest, setExtreme, setRandom } from '../utils/BlueprintFunctions';
getBlueprint,
blueprintTooltip,
setPercent,
getPercent,
setRandom,
specialToolTip
} from '../utils/BlueprintFunctions';
const MODIFICATIONS_COMPARATOR = (mod1, mod2) => {
return mod1.props.name.localeCompare(mod2.props.name);
};
/** /**
* Modifications menu * Modifications menu
*/ */
export default class ModificationsMenu extends TranslatedComponent { export default class ModificationsMenu extends TranslatedComponent {
static propTypes = { static propTypes = {
ship: PropTypes.object.isRequired, ship: PropTypes.object.isRequired,
m: PropTypes.object.isRequired, m: PropTypes.object.isRequired,
marker: PropTypes.string.isRequired, marker: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired
modButton:PropTypes.object
}; };
/** /**
@@ -41,24 +30,14 @@ export default class ModificationsMenu extends TranslatedComponent {
this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this); this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this);
this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this); this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this);
this._rollFifty = this._rollFifty.bind(this); this._rollWorst = this._rollWorst.bind(this);
this._rollRandom = this._rollRandom.bind(this); this._rollRandom = this._rollRandom.bind(this);
this._rollBest = this._rollBest.bind(this); this._rollBest = this._rollBest.bind(this);
this._rollWorst = this._rollWorst.bind(this); this._rollExtreme = this._rollExtreme.bind(this);
this._reset = this._reset.bind(this); this._reset = this._reset.bind(this);
this._keyDown = this._keyDown.bind(this);
this.modItems = [];// Array to hold various element refs (<li>, <div>, <ul>, etc.)
this.firstModId = null;
this.firstBPLabel = null;// First item in mod menu
this.lastModId = null;
this.selectedModId = null;
this.selectedSpecialId = null;
this.lastNeId = null;// Last number editor id. Used to set focus to last number editor when shift-tab pressed on first element in mod menu.
this.modValDidChange = false; // used to determine if component update was caused by change in modification value.
this._handleModChange = this._handleModChange.bind(this);
this.state = { this.state = {
blueprintMenuOpened: !(props.m.blueprint && props.m.blueprint.name), blueprintMenuOpened: false,
specialMenuOpened: false specialMenuOpened: false
}; };
} }
@@ -73,6 +52,7 @@ export default class ModificationsMenu extends TranslatedComponent {
const { m } = props; const { m } = props;
const { language, tooltip, termtip } = context; const { language, tooltip, termtip } = context;
const translate = language.translate; const translate = language.translate;
const blueprints = []; const blueprints = [];
for (const blueprintName in Modifications.modules[m.grp].blueprints) { for (const blueprintName in Modifications.modules[m.grp].blueprints) {
const blueprint = getBlueprint(blueprintName, m); const blueprint = getBlueprint(blueprintName, m);
@@ -81,18 +61,14 @@ export default class ModificationsMenu extends TranslatedComponent {
// Grade is a string in the JSON so make it a number // Grade is a string in the JSON so make it a number
grade = Number(grade); grade = Number(grade);
const classes = cn('c', { const classes = cn('c', {
active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade
}); });
const close = this._blueprintSelected.bind(this, blueprintName, grade); const close = this._blueprintSelected.bind(this, blueprintName, grade);
const key = blueprintName + ':' + grade; const key = blueprintName + ':' + grade;
const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp); const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp);
if (classes.indexOf('active') >= 0) this.selectedModId = key; blueprintGrades.unshift(<li key={key} className={classes} style={{ width: '2em' }} onMouseOver={termtip.bind(null, tooltipContent)} onMouseOut={tooltip.bind(null, null)} onClick={close}>{grade}</li>);
blueprintGrades.unshift(<li key={key} tabIndex="0" data-id={key} className={classes} style={{ width: '2em' }} onMouseOver={termtip.bind(null, tooltipContent)} onMouseOut={tooltip.bind(null, null)} onClick={close} onKeyDown={this._keyDown} ref={modItem => this.modItems[key] = modItem}>{grade}</li>);
} }
if (blueprintGrades) { if (blueprintGrades) {
const thisLen = blueprintGrades.length;
if (this.firstModId == null) this.firstModId = blueprintGrades[0].key;
this.lastModId = blueprintGrades[thisLen - 1].key;
blueprints.push(<div key={blueprint.name} className={'select-group cap'}>{translate(blueprint.name)}</div>); blueprints.push(<div key={blueprint.name} className={'select-group cap'}>{translate(blueprint.name)}</div>);
blueprints.push(<ul key={blueprintName}>{blueprintGrades}</ul>); blueprints.push(<ul key={blueprintName}>{blueprintGrades}</ul>);
} }
@@ -100,64 +76,6 @@ export default class ModificationsMenu extends TranslatedComponent {
return blueprints; return blueprints;
} }
/**
* Key down - select module on Enter key, move to next/previous module on Tab/Shift-Tab, close on Esc
* @param {SyntheticEvent} event Event
*
*/
_keyDown(event) {
let className = null;
let elemId = null;
if (event.currentTarget.attributes['class']) className = event.currentTarget.attributes['class'].value;
if (event.currentTarget.attributes['data-id']) elemId = event.currentTarget.attributes['data-id'].value;
if (event.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) {
event.stopPropagation();
if (elemId != null) {
this.modItems[elemId].click();
} else {
event.currentTarget.click();
}
return;
}
if (event.key == 'Tab') {
// Shift-Tab
if(event.shiftKey) {
if (elemId == this.firstModId && elemId != null) {
// Initial modification menu
event.preventDefault();
this.modItems[this.lastModId].focus();
return;
} else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.previousElementSibling == null && this.lastNeId != null && this.modItems[this.lastNeId] != null) {
// shift-tab on first element in modifications menu. set focus to last number editor field if open
event.preventDefault();
this.modItems[this.lastNeId].lastChild.focus();
return;
} else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.previousElementSibling == null) {
// shift-tab on button-inline-menu with no number editor
event.preventDefault();
event.currentTarget.parentElement.lastElementChild.focus();
}
} else {
if (elemId == this.lastModId && elemId != null) {
// Initial modification menu
event.preventDefault();
this.modItems[this.firstModId].focus();
return;
} else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.nextSibling == null && event.currentTarget.nodeName != 'TD') {
// Experimental menu
event.preventDefault();
event.currentTarget.parentElement.firstElementChild.focus();
return;
} else if (event.currentTarget.className == 'cb' && event.currentTarget.parentElement.nextSibling == null) {
event.preventDefault();
this.modItems[this.firstBPLabel].focus();
}
}
}
}
/** /**
* Render the specials * Render the specials
* @param {Object} props React component properties * @param {Object} props React component properties
@@ -168,34 +86,15 @@ export default class ModificationsMenu extends TranslatedComponent {
const { m } = props; const { m } = props;
const { language, tooltip, termtip } = context; const { language, tooltip, termtip } = context;
const translate = language.translate; const translate = language.translate;
const specials = []; const specials = [];
const specialsId = m.missile && Modifications.modules[m.grp]['specials_' + m.missile] ? 'specials_' + m.missile : 'specials'; const specialsId = m.missile && Modifications.modules[m.grp]['specials_' + m.missile] ? 'specials_' + m.missile : 'specials';
if (Modifications.modules[m.grp][specialsId] && Modifications.modules[m.grp][specialsId].length > 0) { if (Modifications.modules[m.grp][specialsId] && Modifications.modules[m.grp][specialsId].length > 0) {
const close = this._specialSelected.bind(this, null); const close = this._specialSelected.bind(this, null);
specials.push(<div tabIndex="0" style={{ cursor: 'pointer', fontWeight: 'bold' }} className={ 'button-inline-menu warning' } key={ 'none' } data-id={ 'none' } onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems['none'] = modItem}>{translate('PHRASE_NO_SPECIAL')}</div>); specials.push(<div style={{ cursor: 'pointer' }} key={ 'none' } onClick={ close }>{translate('PHRASE_NO_SPECIAL')}</div>);
for (const specialName of Modifications.modules[m.grp][specialsId]) { for (const specialName of Modifications.modules[m.grp][specialsId]) {
if (Modifications.specials[specialName].name.search('Legacy') >= 0) {
continue;
}
const classes = cn('button-inline-menu', {
active: m.blueprint && m.blueprint.special && m.blueprint.special.edname == specialName
});
if (classes.indexOf('active') >= 0) this.selectedSpecialId = specialName;
const close = this._specialSelected.bind(this, specialName); const close = this._specialSelected.bind(this, specialName);
if (m.blueprint && m.blueprint.name) { specials.push(<div style={{ cursor: 'pointer' }} key={ specialName } onClick={ close }>{translate(Modifications.specials[specialName].name)}</div>);
let tmp = {};
if (m.blueprint.special) {
tmp = m.blueprint.special;
} else {
tmp = undefined;
}
m.blueprint.special = Modifications.specials[specialName];
let specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, specialName);
m.blueprint.special = tmp;
specials.push(<div tabIndex="0" style={{ cursor: 'pointer' }} className={classes} key={ specialName } data-id={ specialName } onMouseOver={termtip.bind(null, specialTt)} onMouseOut={tooltip.bind(null, null)} onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems[specialName] = modItem}>{translate(Modifications.specials[specialName].name)}</div>);
} else {
specials.push(<div tabIndex="0" style={{ cursor: 'pointer' }} className={classes} key={ specialName } data-id={ specialName }onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems[specialName] = modItem}>{translate(Modifications.specials[specialName].name)}</div>);
}
} }
} }
return specials; return specials;
@@ -208,26 +107,14 @@ export default class ModificationsMenu extends TranslatedComponent {
*/ */
_renderModifications(props) { _renderModifications(props) {
const { m, onChange, ship } = props; const { m, onChange, ship } = props;
const modifiableModifications = [];
const modifications = []; const modifications = [];
for (const modName of Modifications.modules[m.grp].modifications) { for (const modName of Modifications.modules[m.grp].modifications) {
if (!Modifications.modifications[modName].hidden) { if (!Modifications.modifications[modName].hidden) {
const key = modName + (m.getModValue(modName) / 100 || 0); const key = modName + (m.getModValue(modName) / 100 || 0);
const editable = modName !== 'fallofffromrange'; modifications.push(<Modification key={ key } ship={ ship } m={ m } name={ modName } value={ m.getModValue(modName) / 100 || 0 } onChange={ onChange }/>);
const highlight = m.blueprint.grades[m.blueprint.grade].features[modName];
this.lastNeId = modName;
(editable && highlight ? modifiableModifications : modifications).push(
<Modification key={ key } ship={ ship } m={ m } highlight={highlight}
value={m.getPretty(modName) || 0} modItems={this.modItems}
onChange={onChange} onKeyDown={this._keyDown} name={modName}
editable={editable} handleModChange = {this._handleModChange} />
);
} }
} }
return modifications;
modifiableModifications.sort(MODIFICATIONS_COMPARATOR);
modifications.sort(MODIFICATIONS_COMPARATOR);
return modifiableModifications.concat(modifications);
} }
/** /**
@@ -249,9 +136,8 @@ export default class ModificationsMenu extends TranslatedComponent {
const blueprint = getBlueprint(fdname, m); const blueprint = getBlueprint(fdname, m);
blueprint.grade = grade; blueprint.grade = grade;
ship.setModuleBlueprint(m, blueprint); ship.setModuleBlueprint(m, blueprint);
setPercent(ship, m, 100);
this.setState({ blueprintMenuOpened: false, specialMenuOpened: true }); this.setState({ blueprintMenuOpened: false });
this.props.onChange(); this.props.onChange();
} }
@@ -282,15 +168,11 @@ export default class ModificationsMenu extends TranslatedComponent {
} }
/** /**
* Provide a '50%' roll within the information we have * Provide a 'worst' roll within the information we have
*/ */
_rollFifty() { _rollWorst() {
const { m, ship } = this.props; const { m, ship } = this.props;
setPercent(ship, m, 50); setWorst(ship, m);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
this._handleModChange(true);
this.props.onChange(); this.props.onChange();
} }
@@ -300,10 +182,6 @@ export default class ModificationsMenu extends TranslatedComponent {
_rollRandom() { _rollRandom() {
const { m, ship } = this.props; const { m, ship } = this.props;
setRandom(ship, m); setRandom(ship, m);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
this._handleModChange(true);
this.props.onChange(); this.props.onChange();
} }
@@ -312,22 +190,16 @@ export default class ModificationsMenu extends TranslatedComponent {
*/ */
_rollBest() { _rollBest() {
const { m, ship } = this.props; const { m, ship } = this.props;
setPercent(ship, m, 100); setBest(ship, m);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
this._handleModChange(true);
this.props.onChange(); this.props.onChange();
} }
/** /**
* Provide a 'worst' roll within the information we have * Provide an 'extreme' roll within the information we have
*/ */
_rollWorst() { _rollExtreme() {
const { m, ship } = this.props; const { m, ship } = this.props;
setPercent(ship, m, 0); setExtreme(ship, m);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
this._handleModChange(true);
this.props.onChange(); this.props.onChange();
} }
@@ -338,67 +210,10 @@ export default class ModificationsMenu extends TranslatedComponent {
const { m, ship } = this.props; const { m, ship } = this.props;
ship.clearModifications(m); ship.clearModifications(m);
ship.clearModuleBlueprint(m); ship.clearModuleBlueprint(m);
this.selectedModId = null;
this.selectedSpecialId = null;
this.props.onChange(); this.props.onChange();
} }
/**
* set mod did change boolean
* @param {boolean} b Boolean to determine if a change has been made to a module
*/
_handleModChange(b) {
this.modValDidChange = b;
}
/**
* Set focus on first element in modifications menu
* after it first mounts
*/
componentDidMount() {
let firstEleCn = this.modItems['modMainDiv'].children.length > 0 ? this.modItems['modMainDiv'].children[0].className : null;
if (firstEleCn.indexOf('select-group cap') >= 0) {
this.modItems['modMainDiv'].children[1].firstElementChild.focus();
} else {
this.modItems['modMainDiv'].firstElementChild.focus();
}
}
/**
* Set focus on first element in modifications menu
* if component updates, unless update is due to value change
* in a modification
*/
componentDidUpdate() {
if (!this.modValDidChange) {
if (this.modItems['modMainDiv'].children.length > 0) {
if (this.modItems[this.selectedModId]) {
this.modItems[this.selectedModId].focus();
return;
} else if (this.modItems[this.selectedSpecialId]) {
this.modItems[this.selectedSpecialId].focus();
return;
}
let firstEleCn = this.modItems['modMainDiv'].children[0].className;
if (firstEleCn.indexOf('button-inline-menu') >= 0) {
this.modItems['modMainDiv'].firstElementChild.focus();
} else if (firstEleCn.indexOf('select-group cap') >= 0) {
this.modItems['modMainDiv'].children[1].firstElementChild.focus();
}
}
} else {
this._handleModChange(false);// Need to reset if component update due to value change
}
}
/**
* set focus to the modification menu icon after mod menu is unmounted.
*/
componentWillUnmount() {
if (this.props.modButton) {
this.props.modButton.focus();
}
}
/** /**
* Render the list * Render the list
* @return {React.Component} List * @return {React.Component} List
@@ -411,82 +226,66 @@ export default class ModificationsMenu extends TranslatedComponent {
const _toggleBlueprintsMenu = this._toggleBlueprintsMenu; const _toggleBlueprintsMenu = this._toggleBlueprintsMenu;
const _toggleSpecialsMenu = this._toggleSpecialsMenu; const _toggleSpecialsMenu = this._toggleSpecialsMenu;
const _rollFull = this._rollBest; const _rollBest = this._rollBest;
const _rollExtreme = this._rollExtreme;
const _rollWorst = this._rollWorst; const _rollWorst = this._rollWorst;
const _rollFifty = this._rollFifty;
const _rollRandom = this._rollRandom; const _rollRandom = this._rollRandom;
const _reset = this._reset; const _reset = this._reset;
let blueprintLabel; let blueprintLabel;
let haveBlueprint = false; let haveBlueprint = false;
let blueprintTt; let blueprintTt;
let blueprintCv; if (m.blueprint && m.blueprint.name) {
// TODO: Fix this to actually find the correct blueprint.
if (!m.blueprint || !m.blueprint.name || !m.blueprint.fdname || !Modifications.modules[m.grp].blueprints || !Modifications.modules[m.grp].blueprints[m.blueprint.fdname]) {
this.props.ship.clearModuleBlueprint(m);
this.props.ship.clearModuleSpecial(m);
}
if (m.blueprint && m.blueprint.name && Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade]) {
blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade; blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
haveBlueprint = true; haveBlueprint = true;
blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade].engineers, m.grp); blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade].engineers, m.grp);
blueprintCv = getPercent(m);
} }
let specialLabel; let specialLabel;
let specialTt;
if (m.blueprint && m.blueprint.special) { if (m.blueprint && m.blueprint.special) {
specialLabel = translate(m.blueprint.special.name); specialLabel = m.blueprint.special.name;
specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, m.blueprint.special.edname);
} else { } else {
specialLabel = translate('PHRASE_SELECT_SPECIAL'); specialLabel = translate('PHRASE_SELECT_SPECIAL');
} }
const specials = this._renderSpecials(this.props, this.context); const specials = this._renderSpecials(this.props, this.context);
/**
* pnellesen - 05/28/2018 - added additional checks for specials.length below to ensure menus
* display correctly in cases where there are no specials (ex: AFMUs.)
*/
const showBlueprintsMenu = blueprintMenuOpened; const showBlueprintsMenu = blueprintMenuOpened;
const showSpecial = haveBlueprint && specials.length && !blueprintMenuOpened; const showSpecial = haveBlueprint && specials.length && !blueprintMenuOpened;
const showSpecialsMenu = specialMenuOpened && specials.length; const showSpecialsMenu = specialMenuOpened;
const showRolls = haveBlueprint && !blueprintMenuOpened && (!specialMenuOpened || !specials.length); const showRolls = haveBlueprint && !blueprintMenuOpened && !specialMenuOpened;
const showReset = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint; const showReset = !blueprintMenuOpened && !specialMenuOpened;
const showMods = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint; const showMods = !blueprintMenuOpened && !specialMenuOpened;
if (haveBlueprint) {
this.firstBPLabel = blueprintLabel;
} else {
this.firstBPLabel = 'selectBP';
}
return ( return (
<div <div
className={cn('select', this.props.className)} className={cn('select', this.props.className)}
onClick={(e) => e.stopPropagation() } onClick={(e) => e.stopPropagation() }
onContextMenu={stopCtxPropagation} onContextMenu={stopCtxPropagation}
ref={modItem => this.modItems['modMainDiv'] = modItem}
> >
{ showBlueprintsMenu | showSpecialsMenu ? '' : haveBlueprint ? { showBlueprintsMenu ? '' : haveBlueprint ?
<div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{blueprintLabel}</div> : <div className={ cn('section-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu}>{blueprintLabel}</div> :
<div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> } <div className={ cn('section-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> }
{ showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : null } { showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : null }
{ showSpecial & !showSpecialsMenu ? <div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={specialTt ? termtip.bind(null, specialTt) : null} onMouseOut={specialTt ? tooltip.bind(null, null) : null} onClick={_toggleSpecialsMenu} onKeyDown={ this._keyDown }>{specialLabel}</div> : null } { showSpecial ? <div className={ cn('section-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleSpecialsMenu}>{specialLabel}</div> : null }
{ showSpecialsMenu ? specials : null } { showSpecialsMenu ? specials : null }
{ showReset ? <div tabIndex="0" className={'section-menu button-inline-menu warning'} style={{ cursor: 'pointer' }} onClick={_reset} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </div> : null } { showRolls || showReset ?
{ showRolls ? <table style={{ width: '100%', backgroundColor: 'transparent' }}>
<tbody>
<table style={{ width: '100%', backgroundColor: 'transparent' }}> { showRolls ?
<tbody>
{ showRolls ?
<tr> <tr>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: false }) }> { translate('mroll') }: </td> <td> { translate('roll') }: </td>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 0 }) } style={{ cursor: 'pointer' }} onClick={_rollWorst} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('0%') } </td> <td style={{ cursor: 'pointer' }} onClick={_rollWorst} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('worst') } </td>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 50 })} style={{ cursor: 'pointer' }} onClick={_rollFifty} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_FIFTY')} onMouseOut={tooltip.bind(null, null)}> { translate('50%') } </td> <td style={{ cursor: 'pointer' }} onClick={_rollBest}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('best') } </td>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 100 })} style={{ cursor: 'pointer' }} onClick={_rollFull} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('100%') } </td> <td style={{ cursor: 'pointer' }} onClick={_rollExtreme}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_EXTREME')} onMouseOut={tooltip.bind(null, null)}> { translate('extreme') } </td>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === null || blueprintCv % 50 != 0 })} style={{ cursor: 'pointer' }} onClick={_rollRandom} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td> <td style={{ cursor: 'pointer' }} onClick={_rollRandom} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td>
</tr> : null } </tr> : null }
</tbody> { showReset ?
<tr>
<td colSpan={'5'} style={{ cursor: 'pointer' }} onClick={_reset}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </td>
</tr> : null }
</tbody>
</table> : null } </table> : null }
{ showMods ? <hr /> : null }
{ showMods ? { showMods ?
<span onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} > <span onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} >
{ this._renderModifications(this.props) } { this._renderModifications(this.props) }

View File

@@ -35,13 +35,13 @@ export default class Movement extends TranslatedComponent {
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,13 +57,13 @@ 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 */} // Speed
<text x="470" y="30" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcSpeed(eng, fuel, cargo, boost)) + '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>
{/* Pitch */} // Pitch
<text x="355" y="410" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcPitch(eng, fuel, cargo, boost)) + '°/s' : '-'}</text> <text x="355" y="410" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcPitch(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
{/* Roll */} // Roll
<text x="450" y="110" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcRoll(eng, fuel, cargo, boost)) + '°/s' : '-'}</text> <text x="450" y="110" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcRoll(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
{/* Yaw */} // Yaw
<text x="160" y="430" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcYaw(eng, fuel, cargo, boost)) + '°/s' : '-'}</text> <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>);

View File

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

View File

@@ -1,6 +1,8 @@
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';
@@ -174,7 +176,7 @@ export default class OutfittingSubpages extends TranslatedComponent {
<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 == '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 == '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 == '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> <th style={{ width:'25%' }} className={cn({ active: tab == 'defence' })} onClick={this._showTab.bind(this, 'defence')} >{translate('defence')}</th>
</tr> </tr>
</thead> </thead>
</table> </table>

View File

@@ -1,6 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ContainerDimensions from 'react-container-dimensions'; import Measure from 'react-measure';
import * as d3 from 'd3'; import * as d3 from 'd3';
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D']; const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
@@ -27,6 +27,13 @@ export default class PieChart extends Component {
this.colors = CORIOLIS_COLOURS; this.colors = CORIOLIS_COLOURS;
this.arc = d3.arc(); this.arc = d3.arc();
this.arc.innerRadius(0); this.arc.innerRadius(0);
this.state = {
dimensions: {
width: 100,
height: 100
}
};
} }
@@ -34,15 +41,15 @@ export default class PieChart extends Component {
* Generate a slice of the pie chart * Generate a slice of the pie chart
* @param {Object} d the data for this slice * @param {Object} d the data for this slice
* @param {number} i the index of this slice * @param {number} i the index of this slice
* @param {number} width the current width of the parent container
* @returns {Object} the SVG for the slice * @returns {Object} the SVG for the slice
*/ */
sliceGenerator(d, i, width) { sliceGenerator(d, i) {
if (!d || d.value == 0) { if (!d || d.value == 0) {
// Ignore 0 values // Ignore 0 values
return null; return null;
} }
const { width, height } = this.state.dimensions;
const { data } = this.props; const { data } = this.props;
// Push the labels further out from the centre of the slice // Push the labels further out from the centre of the slice
@@ -69,24 +76,22 @@ export default class PieChart extends Component {
* @returns {object} Markup * @returns {object} Markup
*/ */
render() { render() {
return ( const { width, height } = this.state.dimensions;
<ContainerDimensions> const pie = this.pie(this.props.data),
{ ({ width }) => { translate = `translate(${width / 2}, ${width * 0.4})`;
const pie = this.pie(this.props.data),
translate = `translate(${width / 2}, ${width * 0.4})`;
this.arc.outerRadius(width * 0.4); this.arc.outerRadius(width * 0.4);
return (
<div width={width} height={width}> return (
<svg style={{ stroke: 'None' }} width={width} height={width * 0.9}> <Measure width='100%' whitelist={['width', 'top']} onMeasure={ (dimensions) => { this.setState({ dimensions }); }}>
<g transform={translate}> <div width={width} height={width}>
{pie.map((d, i) => this.sliceGenerator(d, i, width))} <svg style={{ stroke: 'None' }} width={width} height={width * 0.9}>
</g> <g transform={translate}>
</svg> {pie.map((d, i) => this.sliceGenerator(d, i))}
</div> </g>
); </svg>
}} </div>
</ContainerDimensions> </Measure>
); );
} }
} }

View File

@@ -1,8 +1,13 @@
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 autoBind from 'auto-bind'; import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
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.
@@ -13,9 +18,6 @@ export default class Pips extends TranslatedComponent {
sys: PropTypes.number.isRequired, sys: PropTypes.number.isRequired,
eng: PropTypes.number.isRequired, eng: PropTypes.number.isRequired,
wep: PropTypes.number.isRequired, wep: PropTypes.number.isRequired,
mcSys: PropTypes.number.isRequired,
mcEng: PropTypes.number.isRequired,
mcWep: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired onChange: PropTypes.func.isRequired
}; };
@@ -26,7 +28,9 @@ export default class Pips extends TranslatedComponent {
*/ */
constructor(props, context) { constructor(props, context) {
super(props); super(props);
autoBind(this); const { sys, eng, wep } = props;
this._keyDown = this._keyDown.bind(this);
} }
/** /**
@@ -70,21 +74,30 @@ export default class Pips extends TranslatedComponent {
} }
} }
/**
* Handle a click
* @param {string} which Which item was clicked
*/
onClick(which) {
if (which == 'SYS') {
this._incSys();
} else if (which == 'ENG') {
this._incEng();
} else if (which == 'WEP') {
this._incWep();
} else if (which == 'RST') {
this._reset();
}
}
/** /**
* Reset the capacitor * Reset the capacitor
*/ */
_reset(isMc) { _reset() {
let { sys, eng, wep, mcSys, mcEng, mcWep } = this.props; let { sys, eng, wep } = this.props;
if (isMc) { if (sys != 2 || eng != 2 || wep != 2) {
if (mcSys || mcEng || mcWep) {
sys -= mcSys;
eng -= mcEng;
wep -= mcWep;
this.props.onChange(sys, eng, wep, 0, 0, 0);
}
} else if (sys != 2 || eng != 2 || wep != 2) {
sys = eng = wep = 2; sys = eng = wep = 2;
this.props.onChange(sys + mcSys, eng + mcEng, wep + mcWep, mcSys, mcEng, mcWep); this.props.onChange(sys, eng, wep);
} }
} }
@@ -92,133 +105,151 @@ export default class Pips extends TranslatedComponent {
* Increment the SYS capacitor * Increment the SYS capacitor
*/ */
_incSys() { _incSys() {
this._inc('sys', false); 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 * Increment the ENG capacitor
*/ */
_incEng() { _incEng() {
this._inc('eng', false); 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 * Increment the WEP capacitor
*/ */
_incWep() { _incWep() {
this._inc('wep', false); let { sys, eng, wep } = this.props;
}
_wrapMcClick(key) { const required = Math.min(1, 4 - wep);
return (event) => { if (required > 0) {
event.stopPropagation();
event.preventDefault();
if (key == 'rst') {
this._reset(true);
} else {
this._inc(key, true);
}
};
}
/**
* Increases a given capacitor
* @param {String} key Pip name to increase (one of 'sys', 'eng', 'wep')
* @param {Boolean} isMc True when increase is by multi crew
*/
_inc(key, isMc) {
if (!['sys', 'eng', 'wep'].includes(key)) {
return;
}
let { sys, eng, wep, mcSys, mcEng, mcWep } = this.props;
let mc = key == 'sys' ? mcSys : (key == 'eng' ? mcEng : mcWep);
let pips = this.props[key] - mc;
let other1 = key == 'sys' ? eng - mcEng : sys - mcSys;
let other2 = key == 'wep' ? eng - mcEng : wep - mcWep;
const required = Math.min(1, 4 - mc - pips);
if (isMc) {
// We can only set full pips in multi-crew also we can only set two pips
if (required > 0.5 && mcSys + mcEng + mcWep < 2) {
if (key == 'sys') {
mcSys += 1;
} else if (key == 'eng') {
mcEng += 1;
} else {
mcWep += 1;
}
}
} else if (required > 0) {
if (required == 0.5) { if (required == 0.5) {
// Take from whichever is larger // Take from whichever is larger
if (other1 > other2) { if (sys > eng) {
other1 -= 0.5; sys -= 0.5;
wep += 0.5;
} else { } else {
other2 -= 0.5; eng -= 0.5;
wep += 0.5;
} }
pips += 0.5;
} else { } else {
// Required is 1 - take from both if possible // Required is 1 - take from both if possible
if (other1 == 0) { if (sys == 0) {
other2 -= 1; eng -= 1;
} else if (other2 == 0) { wep += 1;
other1 -= 1; } else if (eng == 0) {
sys -= 1;
wep += 1;
} else { } else {
other1 -= 0.5; sys -= 0.5;
other2 -= 0.5; eng -= 0.5;
wep += 1;
} }
pips += 1;
} }
this.props.onChange(sys, eng, wep);
} }
sys = mcSys + (key == 'sys' ? pips : other1);
eng = mcEng + (key == 'eng' ? pips : (key == 'sys' ? other1 : other2));
wep = mcWep + (key == 'wep' ? pips : other2);
this.props.onChange(sys, eng, wep, mcSys, mcEng, mcWep);
} }
/** /**
* Set up the rendering for pips * Set up the rendering for pips
* @param {Number} sys the SYS pips * @param {int} sys the SYS pips
* @param {Number} eng the ENG pips * @param {int} eng the ENG pips
* @param {Number} wep the WEP pips * @param {int} wep the WEP pips
* @param {Number} mcSys SYS pips from multi-crew
* @param {Number} mcEng ENG pips from multi-crew
* @param {Number} mcWep WEP pips from multi-crew
* @returns {Object} Object containing the rendering for the pips * @returns {Object} Object containing the rendering for the pips
*/ */
_renderPips(sys, eng, wep, mcSys, mcEng, mcWep) { _renderPips(sys, eng, wep) {
const pipsSvg = { const pipsSvg = {};
SYS: [],
ENG: [],
WEP: [],
};
// Multi-crew pipsSettings actually are included in the overall pip count therefore // SYS
// we can consider [0, sys - mcSys] as normal pipsSettings whilst [sys - mcSys, sys] pipsSvg['SYS'] = [];
// are the multi-crew pipsSettings in what follows. for (let i = 0; i < Math.floor(sys); i++) {
pipsSvg['SYS'].push(<Pip className='full' key={i} />);
}
if (sys > Math.floor(sys)) {
pipsSvg['SYS'].push(<Pip className='half' key={'half'} />);
}
for (let i = Math.floor(sys + 0.5); i < 4; i++) {
pipsSvg['SYS'].push(<Pip className='empty' key={i} />);
}
let pipsSettings = { // ENG
SYS: [sys, mcSys], pipsSvg['ENG'] = [];
ENG: [eng, mcEng], for (let i = 0; i < Math.floor(eng); i++) {
WEP: [wep, mcWep], 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} />);
}
for (let pipName in pipsSettings) { // WEP
let [pips, mcPips] = pipsSettings[pipName]; pipsSvg['WEP'] = [];
for (let i = 0; i < Math.floor(pips - mcPips); i++) { for (let i = 0; i < Math.floor(wep); i++) {
pipsSvg[pipName].push(<Pip key={i} className='full' />); pipsSvg['WEP'].push(<Pip className='full' key={i} />);
} }
if (pips > Math.floor(pips)) { if (wep > Math.floor(wep)) {
pipsSvg[pipName].push(<Pip className='half' key={'half'} />); pipsSvg['WEP'].push(<Pip className='half' key={'half'} />);
} }
for (let i = pips - mcPips; i < Math.floor(pips); i++) { for (let i = Math.floor(wep + 0.5); i < 4; i++) {
pipsSvg[pipName].push(<Pip key={i} className='mc' />); pipsSvg['WEP'].push(<Pip className='empty' key={i} />);
}
for (let i = Math.floor(pips + 0.5); i < 4; i++) {
pipsSvg[pipName].push(<Pip className='empty' key={i} />);
}
} }
return pipsSvg; return pipsSvg;
@@ -229,11 +260,15 @@ export default class Pips extends TranslatedComponent {
* @return {React.Component} contents * @return {React.Component} contents
*/ */
render() { render() {
const { tooltip, termtip } = this.context;
const { formats, translate, units } = this.context.language; const { formats, translate, units } = this.context.language;
const { sys, eng, wep, mcSys, mcEng, mcWep } = this.props; const { sys, eng, wep } = this.props;
const pipsSvg = this._renderPips(sys, eng, wep, mcSys, mcEng, mcWep); const onSysClicked = this.onClick.bind(this, 'SYS');
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>
@@ -241,38 +276,20 @@ export default class Pips extends TranslatedComponent {
<tr> <tr>
<td>&nbsp;</td> <td>&nbsp;</td>
<td>&nbsp;</td> <td>&nbsp;</td>
<td className='clickable' onClick={() => this._inc('eng')} <td className='clickable' onClick={onEngClicked}>{pipsSvg['ENG']}</td>
onContextMenu={this._wrapMcClick('eng')}>{pipsSvg['ENG']}</td>
<td>&nbsp;</td> <td>&nbsp;</td>
</tr> </tr>
<tr> <tr>
<td>&nbsp;</td> <td>&nbsp;</td>
<td className='clickable' onClick={this._incSys} <td className='clickable' onClick={onSysClicked}>{pipsSvg['SYS']}</td>
onContextMenu={this._wrapMcClick('sys')}>{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>
onContextMenu={this._wrapMcClick('eng')}>{translate('ENG')}</td>
<td className='clickable' onClick={this._incWep}
onContextMenu={this._wrapMcClick('wep')}>{pipsSvg['WEP']}</td>
</tr> </tr>
<tr> <tr>
<td>&nbsp;</td> <td>&nbsp;</td>
<td className='clickable' onClick={this._incSys} <td className='clickable' onClick={onSysClicked}>{translate('SYS')}</td>
onContextMenu={this._wrapMcClick('sys')}>{translate('SYS')}</td> <td className='clickable' onClick={onRstClicked}>{translate('RST')}</td>
<td className='clickable' onClick={this._reset.bind(this, false)}> <td className='clickable' onClick={onWepClicked}>{translate('WEP')}</td>
{translate('RST')}
</td>
<td className='clickable' onClick={this._incWep}
onContextMenu={this._wrapMcClick('wep')}>{translate('WEP')}</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td className='clickable secondary' onClick={this._wrapMcClick('rst')}
onMouseEnter={termtip.bind(null, 'PHRASE_MULTI_CREW_CAPACITOR_POINTS')}
onMouseLeave={tooltip.bind(null, null)}>
{translate('RST')}
</td>
<td>&nbsp;</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -32,6 +32,7 @@ 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, bands: PropTypes.array.isRequired,
available: PropTypes.number.isRequired, available: PropTypes.number.isRequired,

View File

@@ -1,6 +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 Ship from '../shipyard/Ship';
import { Ships } from 'coriolis-data/dist'; import { Ships } from 'coriolis-data/dist';
import { Rocket } from './SvgIcons'; import { Rocket } from './SvgIcons';
import Persist from '../stores/Persist'; import Persist from '../stores/Persist';

View File

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

View File

@@ -17,12 +17,12 @@ export default class Slider extends React.Component {
static propTypes = { static propTypes = {
axis: PropTypes.bool, axis: PropTypes.bool,
axisUnit: PropTypes.string,// units (T, M, etc.) axisUnit: PropTypes.string,
max: PropTypes.number, max: PropTypes.number,
min: PropTypes.number, min: PropTypes.number,
onChange: PropTypes.func.isRequired,// function which determins percent value onChange: PropTypes.func.isRequired,
onResize: PropTypes.func, onResize: PropTypes.func,
percent: PropTypes.number.isRequired,// value of slider percent: PropTypes.number.isRequired,
scale: PropTypes.number scale: PropTypes.number
}; };
@@ -35,11 +35,6 @@ export default class Slider extends React.Component {
this._down = this._down.bind(this); this._down = this._down.bind(this);
this._move = this._move.bind(this); this._move = this._move.bind(this);
this._up = this._up.bind(this); this._up = this._up.bind(this);
this._keyup = this._keyup.bind(this);
this._keydown = this._keydown.bind(this);
this._touchstart = this._touchstart.bind(this);
this._touchend = this._touchend.bind(this);
this._updatePercent = this._updatePercent.bind(this); this._updatePercent = this._updatePercent.bind(this);
this._updateDimensions = this._updateDimensions.bind(this); this._updateDimensions = this._updateDimensions.bind(this);
@@ -55,7 +50,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 +69,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
@@ -201,186 +136,31 @@ export default class Slider extends React.Component {
render() { render() {
let outerWidth = this.state.outerWidth; let outerWidth = this.state.outerWidth;
let { axis, axisUnit, min, max, scale } = this.props; let { axis, axisUnit, min, max, scale } = this.props;
let style = { let style = {
width: '100%', width: '100%',
height: axis ? '2.5em' : '1.5em', height: axis ? '2.5em' : '1.5em',
boxSizing: 'border-box' boxSizing: 'border-box'
}; };
if (!outerWidth) { if (!outerWidth) {
return <svg style={style} ref={node => this.node = node} />; return <svg style={style} ref={node => this.node = node} />;
} }
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
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"> return <svg onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onTouchEnd={this._up} style={style} ref={node => this.node = node}>
<rect className='primary' style={{ opacity: 0.3 }} x={margin} y='0.25em' rx='0.3em' ry='0.3em' width={width} height='0.7em' /> <rect className='primary' 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} />
<rect x={margin} width={width} height='100%' fillOpacity='0' style={{ cursor: 'col-resize' }} onMouseDown={this._down} onTouchMove={this._move} onTouchStart={this._down} onTouchEnd={this._touchend} /> <rect x={margin} width={width} height='100%' fillOpacity='0' style={{ cursor: 'col-resize' }} onMouseDown={this._down} onTouchMove={this._move} onTouchStart={this._down} />
{axis && <g style={{ fontSize: '.7em' }}> {axis && <g style={{ fontSize: '.7em' }}>
<text className='primary-disabled' y='3em' x={margin} style={{ textAnchor: 'middle' }}>{min + axisUnit}</text> <text className='primary-disabled' y='3em' x={margin} style={{ textAnchor: 'middle' }}>{min + axisUnit}</text>
<text className='primary-disabled' y='3em' x='50%' style={{ textAnchor: 'middle' }}>{(min + max / 2) + axisUnit}</text> <text className='primary-disabled' y='3em' x='50%' style={{ textAnchor: 'middle' }}>{(min + max / 2) + axisUnit}</text>
<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}
onChange={this.props.onChange}
percent={this.props.percent}
axisUnit={this.props.axisUnit}
scale={this.props.scale}
max={this.props.max}
/>
</div>;
} }
} }
/**
* New component to add keyboard support for sliders - works on all devices (desktop, iOS, Android)
**/
class TextInputBox extends React.Component {
static propTypes = {
axisUnit: PropTypes.string,// units (T, M, etc.)
max: PropTypes.number,
onChange: PropTypes.func.isRequired,// function which determins percent value
percent: PropTypes.number.isRequired,// value of slider
scale: PropTypes.number
};
/**
* Determine if the user is still dragging
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this._handleFocus = this._handleFocus.bind(this);
this._handleBlur = this._handleBlur.bind(this);
this._handleChange = this._handleChange.bind(this);
this._keyup = this._keyup.bind(this);
this.state = this._getInitialState();
}
/**
* Update input value if slider changes will change props/state
* @param {Object} nextProps React Component properites
* @param {Object} nextState React Component state values
*/
componentWillReceiveProps(nextProps, nextState) {
let nextValue = nextProps.percent * nextProps.max;
// See https://stackoverflow.com/questions/32414308/updating-state-on-props-change-in-react-form
if (nextValue !== this.state.inputValue && nextValue <= nextProps.max) {
this.setState({ inputValue: nextValue });
}
}
/**
* Update slider textbox visibility/values if changes are made to slider
* @param {Object} prevProps React Component properites
* @param {Object} prevState React Component state values
*/
componentDidUpdate(prevProps, prevState) {
if (prevState.divStyle.display == 'none' && this.state.divStyle.display == 'block') {
this.enterTimer = setTimeout(() => this.sliderVal.focus(), 10);
}
if (prevProps.max !== this.props.max && this.state.inputValue > this.props.max) {
// they chose a different module
this.setState({ inputValue: this.props.max });
}
if (this.state.inputValue != prevState.inputValue && prevProps.max == this.props.max) {
this.props.onChange(this.state.inputValue / this.props.max);
}
}
/**
* Set initial state for the textbox.
* We may want to rethink this to
* try and make it a stateless component
* @returns {object} React state object with initial values set
*/
_getInitialState() {
return {
divStyle: { display:'none' },
inputStyle: { width:'4em' },
labelStyle: { marginLeft: '.1em' },
maxLength:5,
size:5,
min:0,
tabIndex:-1,
type:'number',
readOnly: true,
inputValue: this.props.percent * this.props.max
};
}
/**
*
* @param {string} val block or none
*/
_setDisplay(val) {
this.setState({
divStyle: { display:val }
});
}
/**
* Update the input value
* when textbox gets focus
*/
_handleFocus() {
this.setState({
inputValue:this._getValue()
});
}
/**
* Update inputValue when textbox loses focus
*/
_handleBlur() {
this._setDisplay('none');
if (this.state.inputValue !== '') {
this.props.onChange(this.state.inputValue / this.props.max);
} else {
this.setState({
inputValue: this.props.percent * this.props.max
});
}
}
/**
* Get the value in the text box
* @returns {number} inputValue Value of the input box
*/
_getValue() {
return this.state.inputValue;
}
/**
* Update and set limits on input box
* values depending on what user
* has selected
*
* @param {SyntheticEvent} event ReactJs onChange event
*/
_handleChange(event) {
if (event.target.value < 0) {
this.setState({ inputValue: 0 });
} else if (event.target.value <= this.props.max) {
this.setState({ inputValue: event.target.value });
} else {
this.setState({ inputValue: this.props.max });
}
}
/**
* Key up handler for input field.
* If user hits Enter key, blur/close the input field
* @param {Event} event Keyboard event
*/
_keyup(event) {
switch (event.key) {
case 'Enter':
this.sliderVal.blur();
return;
default:
return;
}
}
/**
* Get the value in the text box
* @return {React.Component} Text Input component for Slider
*/
render() {
let { axisUnit, onChange, percent, scale } = this.props;
return <div style={this.state.divStyle}><input style={this.state.inputStyle} value={this._getValue()} min={this.state.min} max={this.props.max} onChange={this._handleChange} onKeyUp={this._keyup} tabIndex={this.state.tabIndex} maxLength={this.state.maxLength} size={this.state.size} onBlur={() => {this._handleBlur();}} onFocus={() => {this._handleFocus();}} type={this.state.type} ref={(ip) => this.sliderVal = ip}/><text className="primary upp" style={this.state.labelStyle}>{this.props.axisUnit}</text></div>;
}
}

View File

@@ -6,6 +6,7 @@ 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 { wrapCtxMenu } from '../utils/UtilityFunctions';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
/** /**
* Abstract Slot * Abstract Slot
@@ -39,8 +40,6 @@ export default class Slot extends TranslatedComponent {
this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this)); this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this));
this._getMaxClassLabel = this._getMaxClassLabel.bind(this); this._getMaxClassLabel = this._getMaxClassLabel.bind(this);
this._keyDown = this._keyDown.bind(this);
this.slotDiv = null;
} }
// Must be implemented by subclasses: // Must be implemented by subclasses:
@@ -74,22 +73,6 @@ export default class Slot extends TranslatedComponent {
this.props.onSelect(null,null); this.props.onSelect(null,null);
} }
/** Key Down handler
* @param {SyntheticEvent} event Event
* ToDo: see if this can be moved up
* we do more or less the same thing
* in every section when Enter key is pressed
* on a focusable item
*
*/
_keyDown(event) {
if (event.key == 'Enter') {
if(event.target.className == 'r') {
this._toggleModifications();
}
this.props.onOpen(event);
}
}
/** /**
* Render the slot * Render the slot
* @return {React.Component} The slot * @return {React.Component} The slot
@@ -99,7 +82,6 @@ export default class Slot extends TranslatedComponent {
let translate = language.translate; let translate = language.translate;
let { ship, m, enabled, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props; let { ship, m, enabled, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props;
let slotDetails, modificationsMarker, menu; let slotDetails, modificationsMarker, menu;
let missing = false;
if (!selected) { if (!selected) {
// If not selected then sure that modifications flag is unset // If not selected then sure that modifications flag is unset
@@ -109,11 +91,6 @@ export default class Slot extends TranslatedComponent {
if (m) { if (m) {
slotDetails = this._getSlotDetails(m, enabled, translate, language.formats, language.units); // Must be implemented by sub classes slotDetails = this._getSlotDetails(m, enabled, translate, language.formats, language.units); // Must be implemented by sub classes
modificationsMarker = JSON.stringify(m); modificationsMarker = JSON.stringify(m);
if(typeof m.grp !== 'undefined' || m.grp !== null) {
if(m.grp == "mh" || m.grp == "mm") {
missing = true;
}
}
} else { } else {
slotDetails = <div className={'empty'}>{translate(eligible ? 'emptyrestricted' : 'empty')}</div>; slotDetails = <div className={'empty'}>{translate(eligible ? 'emptyrestricted' : 'empty')}</div>;
modificationsMarker = ''; modificationsMarker = '';
@@ -127,39 +104,33 @@ export default class Slot extends TranslatedComponent {
ship={ship} ship={ship}
m={m} m={m}
marker={modificationsMarker} marker={modificationsMarker}
modButton = {this.modButton}
/>; />;
} else { } else {
menu = <AvailableModulesMenu menu = <AvailableModulesMenu
className={this._getClassNames()} className={this._getClassNames()}
modules={availableModules()} modules={availableModules()}
shipMass={ship.hullMass}
m={m} m={m}
ship={ship}
onSelect={onSelect} onSelect={onSelect}
warning={warning} warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)} diffDetails={diffDetails.bind(ship, this.context.language)}
slotDiv = {this.slotDiv}
/>; />;
} }
} }
// TODO: implement touch dragging // TODO: implement touch dragging
return ( return (
<div className={cn('slot', dropClass, { selected })} onClick={onOpen} onKeyDown={this._keyDown} onContextMenu={this._contextMenu} onDragOver={dragOver} tabIndex="0" ref={slotDiv => this.slotDiv = slotDiv}> <div className={cn('slot', dropClass, { selected })} onClick={onOpen} onContextMenu={this._contextMenu} onDragOver={dragOver}>
{ <div className='details-container'>
// If missing module/hardpoint, set the div container to warning status.
}
<div className={ missing === true ? 'details-container warning' : 'details-container'}>
<div className='sz'>{this._getMaxClassLabel(translate)}</div> <div className='sz'>{this._getMaxClassLabel(translate)}</div>
{slotDetails} {slotDetails}
</div> </div>
{menu} {menu}
</div> </div>
); );
} }
/** /**
* Toggle the modifications flag when selecting the modifications icon * Toggle the modifications flag when selecting the modifications icon
*/ */

View File

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

View File

@@ -35,21 +35,6 @@ export default class StandardSlot extends TranslatedComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this._modificationsSelected = false; this._modificationsSelected = false;
this._keyDown = this._keyDown.bind(this);
this.modButton = null;
this.slotDiv = null;
}
/**
* Handle Enter key
* @param {SyntheticEvent} event KeyDown event
*/
_keyDown(event) {
if (event.key == 'Enter') {
if(event.target.className == 'r') {
this._toggleModifications();
}
this.props.onOpen(event);
}
} }
/** /**
@@ -63,13 +48,7 @@ export default class StandardSlot extends TranslatedComponent {
let m = slot.m; let m = slot.m;
let classRating = m.class + m.rating; let classRating = m.class + m.rating;
let menu; let menu;
let validMods = m == null || !Modifications.modules[m.grp] ? [] : (Modifications.modules[m.grp].modifications || []); let validMods = m == null ? [] : (Modifications.modules[m.grp].modifications || []);
if (m && m.name && m.name === 'Guardian Hybrid Power Plant') {
validMods = [];
}
if (m && m.name && m.name === 'Guardian Power Distributor') {
validMods = [];
}
let showModuleResistances = Persist.showModuleResistances(); let showModuleResistances = Persist.showModuleResistances();
let mass = m.getMass() || m.cargo || m.fuel || 0; let mass = m.getMass() || m.cargo || m.fuel || 0;
@@ -77,9 +56,6 @@ export default class StandardSlot extends TranslatedComponent {
let modTT = translate('modified'); let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) { if (m && m.blueprint && m.blueprint.name) {
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade; modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
if (m.blueprint.special && m.blueprint.special.id >= 0) {
modTT += ', ' + translate(m.blueprint.special.name);
}
modTT = ( modTT = (
<div> <div>
<div>{modTT}</div> <div>{modTT}</div>
@@ -93,11 +69,6 @@ export default class StandardSlot extends TranslatedComponent {
this._modificationsSelected = false; this._modificationsSelected = false;
} }
// If this is a missing module, therefore has the 'info' field, set the warning value on the module to be true when loaded.
if (m.info) {
warning = () => true;
}
const modificationsMarker = JSON.stringify(m); const modificationsMarker = JSON.stringify(m);
if (selected) { if (selected) {
@@ -108,28 +79,26 @@ export default class StandardSlot extends TranslatedComponent {
ship={ship} ship={ship}
m={m} m={m}
marker={modificationsMarker} marker={modificationsMarker}
modButton = {this.modButton}
/>; />;
} else { } else {
menu = <AvailableModulesMenu menu = <AvailableModulesMenu
className='standard' className='standard'
modules={modules} modules={modules}
shipMass={ModuleUtils.isShieldGenerator(m.grp) ? ship.hullMass : ship.unladenMass}
m={m} m={m}
ship={ship}
onSelect={onSelect} onSelect={onSelect}
warning={warning} warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)} diffDetails={diffDetails.bind(ship, this.context.language)}
slotDiv = {this.slotDiv}
/>; />;
} }
} }
return ( return (
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onKeyDown={this._keyDown} onContextMenu={stopCtxPropagation} tabIndex="0" ref={ slotDiv => this.slotDiv = slotDiv }> <div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onContextMenu={stopCtxPropagation}>
<div className={cn('details-container', { warning: warning && warning(slot.m), disabled: m.grp !== 'bh' && !slot.enabled })}> <div className={cn('details-container', { warning: warning && warning(slot.m), disabled: m.grp !== 'bh' && !slot.enabled })}>
<div className={'sz'}>{m.grp == 'bh' ? m.name.charAt(0) : slot.maxClass}</div> <div className={'sz'}>{slot.maxClass}</div>
<div> <div>
<div className={'l'}>{classRating} {m.getInfo() ? translate(m.ukName) : translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }</div> <div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }</div>
<div className={'r'}>{formats.round(mass)}{units.T}</div> <div className={'r'}>{formats.round(mass)}{units.T}</div>
<div/> <div/>
<div className={'cb'}> <div className={'cb'}>
@@ -149,8 +118,7 @@ export default class StandardSlot extends TranslatedComponent {
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null } { showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null } { showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null } { m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
{ m.getInfo() ? <div className='l'>{translate(m.getInfo())}</div> : null } { validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
{ m.getInfo() ? <div className='r'></div> : 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> </div>
</div> </div>

View File

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

View File

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

View File

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

View File

@@ -1,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 Measure from 'react-measure';
import { BarChart, Bar, XAxis, YAxis, LabelList } from 'recharts'; import { BarChart, Bar, XAxis, YAxis } from 'recharts';
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D']; const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
const LABEL_COLOUR = '#000000'; const LABEL_COLOUR = '#000000';
@@ -17,6 +17,7 @@ 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
@@ -31,6 +32,13 @@ export default class VerticalBarChart extends TranslatedComponent {
super(props); super(props);
this._termtip = this._termtip.bind(this); this._termtip = this._termtip.bind(this);
this.state = {
dimensions: {
width: 300,
height: 300
}
};
} }
/** /**
@@ -38,6 +46,7 @@ export default class VerticalBarChart extends TranslatedComponent {
* @returns {Object} the markup * @returns {Object} the markup
*/ */
render() { render() {
const { width, height } = this.state.dimensions;
const { tooltip, termtip } = this.context; const { tooltip, termtip } = this.context;
// Calculate maximum for Y // Calculate maximum for Y
@@ -47,19 +56,15 @@ export default class VerticalBarChart extends TranslatedComponent {
const localMax = Math.max(dataMax, yMax); const localMax = Math.max(dataMax, yMax);
return ( return (
<ContainerDimensions> <Measure whitelist={['width', 'top']} onMeasure={ (dimensions) => this.setState({ dimensions }) }>
{ ({ width }) => ( <div width='100%'>
<div width='100%'> <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)}> </BarChart>
<LabelList dataKey='value' position='insideTop'/> </div>
</Bar> </Measure>
</BarChart>
</div>
)}
</ContainerDimensions>
); );
} }
@@ -78,3 +83,29 @@ 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>
);
}
};

View File

@@ -1,8 +1,13 @@
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';
const DAMAGE_DEALT_COLORS = ['#FFFFFF', '#FF0000', '#00FF00', '#7777FF', '#FFFF00', '#FF00FF', '#00FFFF', '#777777']; const DAMAGE_DEALT_COLORS = ['#FFFFFF', '#FF0000', '#00FF00', '#7777FF', '#FFFF00', '#FF00FF', '#00FFFF', '#777777'];
@@ -69,7 +74,7 @@ export default class WeaponDamageChart extends TranslatedComponent {
* Calculate the maximum range of a ship's weapons * Calculate the maximum range of a ship's weapons
* @param {Object} ship The ship * @param {Object} ship The ship
* @returns {int} The maximum range, in metres * @returns {int} The maximum range, in metres
*/ */
_calcMaxRange(ship) { _calcMaxRange(ship) {
let maxRange = 1000; // Minimum let maxRange = 1000; // Minimum
for (let i = 0; i < ship.hardpoints.length; i++) { for (let i = 0; i < ship.hardpoints.length; i++) {
@@ -179,7 +184,7 @@ export default class WeaponDamageChart extends TranslatedComponent {
const code = `${ship.toString()}:${opponent.toString()}`; const code = `${ship.toString()}:${opponent.toString()}`;
return ( return (
<div> <span>
<LineChart <LineChart
xMax={maxRange} xMax={maxRange}
yMax={this.state.maxDps} yMax={this.state.maxDps}
@@ -193,7 +198,7 @@ export default class WeaponDamageChart extends TranslatedComponent {
points={200} points={200}
code={code} code={code}
/> />
</div> </span>
); );
} }
} }

View File

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

View File

@@ -1,16 +0,0 @@
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';

File diff suppressed because one or more lines are too long

View File

@@ -1,16 +1,16 @@
export const formats = { export const formats = {
decimal: ',', decimal: ',',
thousands: '.', thousands: '.',
grouping: [3], grouping: [3],
currency: ['', ' €'], currency: ['', ' €'],
dateTime: '%A, der %e. %B %Y, %X', dateTime: '%A, der %e. %B %Y, %X',
date: '%d.%m.%Y', date: '%d.%m.%Y',
time: '%H:%M:%S', time: '%H:%M:%S',
periods: ['AM', 'PM'], // unused periods: ['AM', 'PM'], // unused
days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'], days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
shortDays: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'], shortDays: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
shortMonths: ['Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'] shortMonths: ['Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']
}; };
export { default as terms } from './de.json'; export { default as terms } from './de.json';

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -59,7 +59,7 @@
"empty all": "vide tout", "empty all": "vide tout",
"Enter Name": "Entrer nom", "Enter Name": "Entrer nom",
"Explorer": "explorateur", "Explorer": "explorateur",
"farthest range": "gamme la plus rapide", "fastest 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",

View File

@@ -1,16 +0,0 @@
export const formats = {
decimal: '.',
thousands: ',',
grouping: [3],
currency: ['₩', ''],
dateTime: '%a %b %e %X %Y',
date: '%Y/%m/%d',
time: '%H:%M:%S',
periods: ['오전', '오후'],
days: ['일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일'],
shortDays: ['일', '월', '화', '수', '목', '금', '토'],
months: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
shortMonths: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월']
};
export { default as terms } from './ko.json';

File diff suppressed because one or more lines are too long

View File

@@ -1,16 +0,0 @@
export const formats = {
decimal: ',',
thousands: '.',
grouping: [3],
currency: ['$', ''],
dateTime: '%A, %e de %B de %Y, %X',
date: '%d/%m/%Y',
time: '%H:%M:%S',
periods: ['AM', 'PM'],
days: ['domingo', 'segunda', 'terça', 'quarta', 'quinta', 'sexta', 'sábado'],
shortDays: ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sab'],
months: ['janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'],
shortMonths: ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez']
};
export { default as terms } from './pt.json';

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,12 @@
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';
import Coriolis from './Coriolis'; import Coriolis from './Coriolis';
// import TapEventPlugin from 'react/lib/TapEventPlugin';
// import EventPluginHub from 'react/lib/EventPluginHub';
// onTouchTap not ready for primetime yet, too many issues with preventing default
// EventPluginHub.injection.injectEventPluginsByName({ TapEventPlugin });
render(<Coriolis />, document.getElementById('coriolis')); render(<Coriolis />, document.getElementById('coriolis'));

View File

@@ -6,6 +6,7 @@ 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
@@ -22,79 +23,24 @@ export default class AboutPage extends Page {
* @return {React.Component} The page contents * @return {React.Component} The page contents
*/ */
renderPage() { renderPage() {
return ( return <div className={'page'} style={{ textAlign: 'left', maxWidth: 800, margin: '0 auto' }}>
<div <h1><CoriolisLogo style={{ marginRight: '0.4em' }} className='xl'/><span className='warning'>Coriolis EDCD Edition</span></h1>
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> <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>
This is a clone of the Coriolis project, whose original author is <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>
currently unable to maintain it. This clone is maintained by the{' '} <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>
<a href="http://edcd.github.io/">EDCD community</a>. <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>
</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 <a style={{ display: 'block', textDecoration: 'none' }} href='https://github.com/EDCD/coriolis' target='_blank' title='Coriolis Github Project'>
style={{ display: 'block', textDecoration: 'none' }} <GitHub style={{ margin: '0.4em' }} className='l fg xl'/>
href="https://github.com/EDCD/coriolis" <h2 style={{ margin: 0, textDecoration: 'none' }}>Github</h2>
target="_blank" github.com/EDCD/coriolis
title="Coriolis Github Project" </a>
>
<GitHub style={{ margin: '0.4em' }} className="l fg xl" />
<h2 style={{ margin: 0, textDecoration: 'none' }}>Github</h2>
github.com/EDCD/coriolis
</a>
<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>
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> <p>You can chat to us on our <a href='https://discord.gg/0uwCh6R62aPRjk9w' target='_blank'>EDCD Discord server</a>.</p>
You can chat to us on our{' '} </div>;
<a href="https://discord.gg/0uwCh6R62aPRjk9w" target="_blank">
EDCD Discord server
</a>
.
</p>
</div>
);
} }
} }

View File

@@ -13,14 +13,7 @@ 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 { import { FloppyDisk, Bin, Download, Embed, Rocket, LinkIcon } from '../components/SvgIcons';
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');
@@ -49,6 +42,7 @@ 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
@@ -87,13 +81,7 @@ 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( builds.push(this._createBuild(shipId, buildName, allBuilds[shipId][buildName]));
this._createBuild(
shipId,
buildName,
allBuilds[shipId][buildName]
)
);
} }
} }
} }
@@ -101,9 +89,7 @@ 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 => comparisonData.builds.forEach((b) => builds.push(this._createBuild(b.shipId, b.buildName)));
builds.push(this._createBuild(b.shipId, b.buildName))
);
saved = true; saved = true;
newName = name; newName = name;
} }
@@ -115,7 +101,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] = {};
@@ -132,9 +118,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;
@@ -169,18 +155,17 @@ 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) { if (!code) { // No build found
// 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
@@ -199,18 +184,13 @@ 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( this.context.showModal(<ModalCompare onSelect={this._buildsSelected} builds={this.state.builds} />);
<ModalCompare
onSelect={this._buildsSelected}
builds={this.state.builds}
/>
);
} }
/** /**
@@ -244,7 +224,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')) {
@@ -282,7 +262,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;
@@ -292,7 +272,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 {
@@ -341,7 +321,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);
} }
@@ -368,20 +348,14 @@ 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 ( return loc.protocol + '//' + loc.host + '/comparison?code=' + encodeURIComponent(code);
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()}/>);
} }
/** /**
@@ -391,25 +365,18 @@ 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( ShortenUrl(url,
url, (shortenedUrl) => callback(comparisonBBCode(translate, formats, facets, builds, shortenedUrl)),
shortenedUrl => (error) => callback(comparisonBBCode(translate, formats, facets, builds, url))
callback(
comparisonBBCode(translate, formats, facets, builds, shortenedUrl)
),
error =>
callback(comparisonBBCode(translate, formats, facets, builds, url))
); );
}; };
this.context.showModal( this.context.showModal(<ModalExport
<ModalExport title={translate('forum') + ' BBCode'}
title={translate('forum') + ' BBCode'} generator={generator}
generator={generator} />);
/>
);
} }
/** /**
@@ -442,8 +409,7 @@ 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) { if (this.context.route !== nextContext.route) { // Only reinit state if the route has changed
// Only reinit state if the route has changed
this.setState(this._initState(nextContext)); this.setState(this._initState(nextContext));
} }
} }
@@ -453,10 +419,7 @@ 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( this.persistListener = Persist.addListener('discounts', this._updateDiscounts);
'discounts',
this._updateDiscounts
);
} }
/** /**
@@ -481,132 +444,65 @@ 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 { let { newName, name, saved, builds, facets, predicate, desc, chartWidth } = this.state;
newName,
name,
saved,
builds,
facets,
predicate,
desc,
chartWidth
} = this.state;
if (this.state.compareMode) { if (this.state.compareMode) {
compareHeader = ( compareHeader = <tr>
<tr> <td className='head'>{translate('comparison')}</td>
<td className="head">{translate('comparison')}</td> <td>
<td> <input value={newName} onChange={this._onNameChange} placeholder={translate('Enter Name')} maxLength='50' />
<input <button onClick={this._save} disabled={!newName || newName == 'all' || saved}>
value={newName} <FloppyDisk className='lg'/><span className='button-lbl'>{translate('save')}</span>
onChange={this._onNameChange} </button>
placeholder={translate('Enter Name')} <button onClick={this._delete} disabled={name == 'all' || !saved}><Bin className='lg warning'/></button>
maxLength="50" <button onClick={this._selectBuilds}>
/> <Rocket className='lg'/><span className='button-lbl'>{translate('builds')}</span>
<button </button>
onClick={this._save} <button className='r' onClick={this._genPermalink} disabled={builds.length == 0}>
disabled={!newName || newName == 'all' || saved} <LinkIcon className='lg'/><span className='button-lbl'>{translate('permalink')}</span>
> </button>
<FloppyDisk className="lg" /> <button className='r' onClick={this._genBBcode} disabled={builds.length == 0}>
<span className="button-lbl">{translate('save')}</span> <Embed className='lg'/><span className='button-lbl'>{translate('forum')}</span>
</button> </button>
<button onClick={this._delete} disabled={name == 'all' || !saved}> </td>
<Bin className="lg warning" /> </tr>;
</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 = ( compareHeader = <tr>
<tr> <td className='head'>{translate('comparison')}</td>
<td className="head">{translate('comparison')}</td> <td>
<td> <h3>{name}</h3>
<h3>{name}</h3> <button className='r' onClick={this._import}><Download className='lg'/>{translate('import')}</button>
<button className="r" onClick={this._import}> </td>
<Download className="lg" /> </tr>;
{translate('import')}
</button>
</td>
</tr>
);
} }
return ( return (
<div <div className={'page'} style={{ fontSize: this.context.sizeRatio + 'em' }}>
className={'page'} <table id='comparison'>
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 <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)}>
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 <ComparisonTable builds={builds} facets={facets} onSort={this._sortShips} predicate={predicate} desc={desc} />
builds={builds}
facets={facets}
onSort={this._sortShips}
predicate={predicate}
desc={desc}
/>
{!builds.length ? ( {!builds.length ?
<div className="chart" ref={node => (this.chartRef = node)}> <div className='chart' ref={node => this.chartRef = node}>{translate('PHRASE_NO_BUILDS')}</div> :
{translate('PHRASE_NO_BUILDS')} facets.filter((f) => f.active).map((f, i) =>
</div> <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>
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}
@@ -619,8 +515,8 @@ export default class ComparisonPage extends Page {
desc={desc} desc={desc}
/> />
</div> </div>
))
)} )}
</div> </div>
); );
} }

View File

@@ -45,9 +45,6 @@ export default class ErrorDetails extends React.Component {
return <div className='error'> return <div className='error'>
<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>
Import Error handling has been improved, but still isn't perfect. <br/>MOST Import failures are a result of missing modules in Coriolis, <br />OR incorrect import strings generated by third party apps. If you're seeing this page, we may have failed to handle the errors in your import correctly. Please see the data output below, specifically the 'scriptUrl:' section if it's there and then if you feel confident enough, please check the github issues page linked below and see if there is a similar issue already logged. If not, please create a new issue with the data below. If you're not confident, please ask for help on the Coriolis Channel of the EDCD Discord server.
<br/>
<br/>
<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/>

View File

@@ -5,6 +5,7 @@ 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
@@ -21,10 +22,6 @@ export default class NotFoundPage extends Page {
* @return {React.Component} The page contents * @return {React.Component} The page contents
*/ */
renderPage() { renderPage() {
return ( return <div className='page' style={{ marginTop: 30 }}>Page <small>{this.context.route.path}</small> Not Found</div>;
<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

View File

@@ -51,25 +51,23 @@ export default class Page extends React.Component {
} }
/** /**
* Update the window title upon mount * Pages are 'pure' components that only render when props, state, or context changes.
* 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
*/ */
componentWillMount() { shouldComponentUpdate(np, ns, nc) {
document.title = this.state.title || 'Coriolis'; return !shallowEqual(this.props, np) || !shallowEqual(this.state, ns) || !shallowEqual(this.context, nc);
} }
/** /**
* Update the window title upon mount * Update the window title upon mount
*/ */
componentDidMount() { componentWillMount() {
document.title = this.state.title || 'Coriolis'; document.title = this.state.title || 'Coriolis';
try {
(window.adsbygoogle = window.adsbygoogle || []).push({
google_ad_client: "ca-pub-3709458261881414",
enable_page_level_ads: true
});
} catch (error) {
}
} }
/** /**

View File

@@ -22,11 +22,9 @@ function countHp(slot) {
*/ */
function countInt(slot) { function countInt(slot) {
let crEligible = !slot.eligible || slot.eligible.cr; let crEligible = !slot.eligible || slot.eligible.cr;
this.int[slot.maxClass - 1]++; // Subtract 1 since there is no Class 0 Internal compartment this.int[slot.maxClass - 1]++; // Subtract 1 since there is no Class 0 Internal compartment
this.intCount++; this.intCount++;
this.maxCargo += crEligible ? this.maxCargo += crEligible ? ModuleUtils.findInternal('cr', slot.maxClass, 'E').cargo : 0;
ModuleUtils.findInternal('cr', slot.maxClass, 'E').cargo :
0;
// if no eligiblity, then assume pce // if no eligiblity, then assume pce
let passSlotType = null; let passSlotType = null;
@@ -44,9 +42,7 @@ function countInt(slot) {
passSlotType = 'pcq'; passSlotType = 'pcq';
passSlotRating = 'B'; passSlotRating = 'B';
} }
let passengerBay = passSlotType ? let passengerBay = passSlotType ? ModuleUtils.findInternal(passSlotType, slot.maxClass, passSlotRating) : null;
ModuleUtils.findMaxInternal(passSlotType, slot.maxClass, passSlotRating) :
null;
this.maxPassengers += passengerBay ? passengerBay.passengers : 0; this.maxPassengers += passengerBay ? passengerBay.passengers : 0;
} }
@@ -61,27 +57,23 @@ function shipSummary(shipId, shipData) {
id: shipId, id: shipId,
hpCount: 0, hpCount: 0,
intCount: 0, intCount: 0,
beta: shipData.beta,
maxCargo: 0, maxCargo: 0,
maxPassengers: 0, 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, standard: shipData.slots.standard,
agility: agility: shipData.properties.pitch + shipData.properties.yaw + shipData.properties.roll
shipData.properties.pitch +
shipData.properties.yaw +
shipData.properties.roll
}; };
Object.assign(summary, shipData.properties); Object.assign(summary, shipData.properties);
let ship = new Ship(shipId, shipData.properties, shipData.slots); let ship = new Ship(shipId, shipData.properties, shipData.slots);
// Build Ship // Build Ship
ship.buildWith(shipData.defaults); // Populate with stock/default components ship.buildWith(shipData.defaults); // Populate with stock/default components
ship.hardpoints.forEach(countHp.bind(summary)); // Count Hardpoints by class ship.hardpoints.forEach(countHp.bind(summary)); // Count Hardpoints by class
ship.internal.forEach(countInt.bind(summary)); // Count Internal Compartments by class ship.internal.forEach(countInt.bind(summary)); // Count Internal Compartments by class
summary.retailCost = ship.totalCost; // Record Stock/Default/retail cost summary.retailCost = ship.totalCost; // Record Stock/Default/retail cost
ship.optimizeMass({ pd: '1D' }); // Optimize Mass with 1D PD for maximum possible jump range ship.optimizeMass({ pd: '1D' }); // Optimize Mass with 1D PD for maximum possible jump range
summary.maxJumpRange = ship.unladenRange; // Record Jump Range summary.maxJumpRange = ship.unladenRange; // Record Jump Range
// Best thrusters // Best thrusters
let th; let th;
@@ -105,6 +97,7 @@ 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;
/** /**
@@ -126,14 +119,12 @@ export default class ShipyardPage extends Page {
title: 'Coriolis EDCD Edition - Shipyard', title: 'Coriolis EDCD Edition - Shipyard',
shipPredicate: 'name', shipPredicate: 'name',
shipDesc: true, shipDesc: true,
shipSummaries: ShipyardPage.cachedShipSummaries, shipSummaries: ShipyardPage.cachedShipSummaries
compare: {},
groupCompared: false,
}; };
} }
/** /**
* Higlight the current ship in the table on mouse over * Higlight the current ship in the table
* @param {String} shipId Ship Id * @param {String} shipId Ship Id
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
@@ -142,24 +133,6 @@ 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
@@ -172,15 +145,12 @@ export default class ShipyardPage extends Page {
shipPredicateIndex = undefined; shipPredicateIndex = undefined;
} }
if ( if (this.state.shipPredicate == shipPredicate && this.state.shipPredicateIndex == shipPredicateIndex) {
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
@@ -189,60 +159,55 @@ 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) { _shipRowElement(s, translate, u, fInt, fRound, highlight) {
let noTouch = this.context.noTouch; let noTouch = this.context.noTouch;
return ( return <tr
<tr
key={s.id} key={s.id}
style={{ height: '1.5em' }} style={{ height: '1.5em' }}
className={cn({ className={cn({ highlighted: noTouch && this.state.shipId === s.id, alt: highlight })}
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">{fInt(s.retailCost)}</td> <td className='ri'>{s.manufacturer}</td>
<td className="ri cap">{translate(SizeMap[s.class])}</td> <td className='ri'>{fInt(s.retailCost)}</td>
<td className="ri">{fInt(s.crew)}</td> <td className='ri cap'>{translate(SizeMap[s.class])}</td>
<td className="ri">{s.masslock}</td> <td className='ri'>{fInt(s.crew)}</td>
<td className="ri">{fInt(s.agility)}</td> <td className='ri'>{s.masslock}</td>
<td className="ri">{fInt(s.hardness)}</td> <td className='ri'>{fInt(s.agility)}</td>
<td className="ri">{fInt(s.hullMass)}</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.baseArmour)}</td> <td className='ri'>{fInt(s.boost)}</td>
<td className="ri">{fInt(s.baseShieldStrength)}</td> <td className='ri'>{fInt(s.baseArmour)}</td>
<td className="ri">{fInt(s.topSpeed)}</td> <td className='ri'>{fInt(s.baseShieldStrength)}</td>
<td className="ri">{fInt(s.topBoost)}</td> <td className='ri'>{fInt(s.topSpeed)}</td>
<td className="ri">{fRound(s.maxJumpRange)}</td> <td className='ri'>{fInt(s.topBoost)}</td>
<td className="ri">{fInt(s.maxCargo)}</td> <td className='ri'>{fRound(s.maxJumpRange)}</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({ disabled: !s.hp[1] })}>{s.hp[1]}</td>
<td className={cn({ disabled: !s.hp[2] })}>{s.hp[2]}</td> <td className={cn({ disabled: !s.hp[2] })}>{s.hp[2]}</td>
<td className={cn({ disabled: !s.hp[3] })}>{s.hp[3]}</td> <td className={cn({ disabled: !s.hp[3] })}>{s.hp[3]}</td>
<td className={cn({ disabled: !s.hp[4] })}>{s.hp[4]}</td> <td className={cn({ disabled: !s.hp[4] })}>{s.hp[4]}</td>
<td className={cn({ disabled: !s.hp[0] })}>{s.hp[0]}</td> <td className={cn({ disabled: !s.hp[0] })}>{s.hp[0]}</td>
<td className={cn({ disabled: !s.int[0] })}>{s.int[0]}</td> <td className={cn({ disabled: !s.int[0] })}>{s.int[0]}</td>
<td className={cn({ disabled: !s.int[1] })}>{s.int[1]}</td> <td className={cn({ disabled: !s.int[1] })}>{s.int[1]}</td>
<td className={cn({ disabled: !s.int[2] })}>{s.int[2]}</td> <td className={cn({ disabled: !s.int[2] })}>{s.int[2]}</td>
<td className={cn({ disabled: !s.int[3] })}>{s.int[3]}</td> <td className={cn({ disabled: !s.int[3] })}>{s.int[3]}</td>
<td className={cn({ disabled: !s.int[4] })}>{s.int[4]}</td> <td className={cn({ disabled: !s.int[4] })}>{s.int[4]}</td>
<td className={cn({ disabled: !s.int[5] })}>{s.int[5]}</td> <td className={cn({ disabled: !s.int[5] })}>{s.int[5]}</td>
<td className={cn({ disabled: !s.int[6] })}>{s.int[6]}</td> <td className={cn({ disabled: !s.int[6] })}>{s.int[6]}</td>
<td className={cn({ disabled: !s.int[7] })}>{s.int[7]}</td> <td className={cn({ disabled: !s.int[7] })}>{s.int[7]}</td>
</tr> </tr>;
);
} }
/** /**
@@ -255,9 +220,8 @@ export default class ShipyardPage extends Page {
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 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}
@@ -274,8 +238,7 @@ export default class ShipyardPage extends Page {
// Sort shipsOverview // Sort shipsOverview
shipSummaries.sort((a, b) => { shipSummaries.sort((a, b) => {
let valA = a[shipPredicate], let valA = a[shipPredicate], valB = b[shipPredicate];
valB = b[shipPredicate];
if (shipPredicateIndex != undefined) { if (shipPredicateIndex != undefined) {
valA = valA[shipPredicateIndex]; valA = valA[shipPredicateIndex];
@@ -288,16 +251,7 @@ export default class ShipyardPage extends Page {
valB = val; valB = val;
} }
if (groupCompared) { if(valA == valB) {
if (compare[a.id] && !compare[b.id]) {
return -1;
}
if (!compare[a.id] && compare[b.id]) {
return 1;
}
}
if (valA == valB) {
if (a.name > b.name) { if (a.name > b.name) {
return 1; return 1;
} else { } else {
@@ -314,310 +268,130 @@ 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) {
detailRows[i] = this._shipRowElement( let shipSortValue = s[shipPredicate];
s, if( shipPredicateIndex != undefined ) {
translate, shipSortValue = shipSortValue[shipPredicateIndex];
units, }
fInt,
formats.f1, if( shipSortValue != lastShipSortValue ) {
); 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({ className={cn({ highlighted: noTouch && this.state.shipId === s.id, alt: backgroundHighlight })}
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"> <td className='le'><Link href={'/outfit/' + s.id}>{s.name}</Link></td>
<Link href={'/outfit/' + s.id}>{s.name} {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 className="content-wrapper"> <div style={{ whiteSpace: 'nowrap', margin: '0 auto', fontSize: '0.8em', position: 'relative', display: 'inline-block', maxWidth: '100%' }}>
<div className="shipyard-table-wrapper"> <table style={{ width: '12em', position: 'absolute', zIndex: 1 }}>
<table style={{width: '12em', position: 'absolute', zIndex: 1}} className="shipyard-table">
<thead> <thead>
<tr> <tr>
<th className="le rgt">&nbsp;</th> <th className='le rgt'>&nbsp;</th>
</tr> </tr>
<tr className="main"> <tr className='main'>
<th className="sortable le rgt" onClick={sortShips('name')}> <th className='sortable le rgt' onClick={sortShips('name')}>{translate('ship')}</th>
{translate('ship')}
</th>
</tr> </tr>
<tr> <tr>
<th className="le rgt invisible">{units['m/s']}</th> <th className='le rgt invisible'>{units['m/s']}</th>
</tr> </tr>
</thead> </thead>
<tbody onMouseLeave={this._highlightShip.bind(this, null)}> <tbody onMouseLeave={this._highlightShip.bind(this, null)}>
{shipRows} {shipRows}
</tbody> </tbody>
</table> </table>
<div style={{ overflowX: 'auto', maxWidth: '100%' }}> <div style={{ overflowX: 'scroll', maxWidth: '100%' }}>
<table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }} className="shipyard-table"> <table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }}>
<thead> <thead>
<tr className="main"> <tr className='main'>
<th>&nbsp;</th> <th rowSpan={3} className='sortable' onClick={sortShips('manufacturer')}>{translate('manufacturer')}</th>
<th <th>&nbsp;</th>
rowSpan={3} <th rowSpan={3} className='sortable' onClick={sortShips('class')}>{translate('size')}</th>
className="sortable" <th rowSpan={3} className='sortable' onClick={sortShips('crew')}>{translate('crew')}</th>
onClick={sortShips('class')} <th rowSpan={3} className='sortable' onMouseEnter={termtip.bind(null, 'mass lock factor')} onMouseLeave={hide} onClick={sortShips('masslock')} >{translate('MLF')}</th>
> <th rowSpan={3} className='sortable' onClick={sortShips('agility')}>{translate('agility')}</th>
{translate('size')} <th rowSpan={3} className='sortable' onMouseEnter={termtip.bind(null, 'hardness')} onMouseLeave={hide} onClick={sortShips('hardness')}>{translate('hrd')}</th>
</th> <th>&nbsp;</th>
<th <th colSpan={4}>{translate('base')}</th>
rowSpan={3} <th colSpan={5}>{translate('max')}</th>
className="sortable" <th className='lft' colSpan={6}></th>
onClick={sortShips('crew')} <th className='lft' colSpan={5}></th>
> <th className='lft' colSpan={8}></th>
{translate('crew')} </tr>
</th> <tr>
<th <th className='sortable lft' onClick={sortShips('retailCost')}>{translate('cost')}</th>
rowSpan={3} <th className='sortable lft' onClick={sortShips('hullMass')}>{translate('hull')}</th>
className="sortable" <th className='sortable lft' onClick={sortShips('speed')}>{translate('speed')}</th>
onMouseEnter={termtip.bind(null, 'mass lock factor')} <th className='sortable' onClick={sortShips('boost')}>{translate('boost')}</th>
onMouseLeave={hide} <th className='sortable' onClick={sortShips('baseArmour')}>{translate('armour')}</th>
onClick={sortShips('masslock')} <th className='sortable' onClick={sortShips('baseShieldStrength')}>{translate('shields')}</th>
>
{translate('MLF')}
</th>
<th
rowSpan={3}
className="sortable"
onClick={sortShips('agility')}
>
{translate('agility')}
</th>
<th
rowSpan={3}
className="sortable"
onMouseEnter={termtip.bind(null, 'hardness')}
onMouseLeave={hide}
onClick={sortShips('hardness')}
>
{translate('hrd')}
</th>
<th>&nbsp;</th>
<th colSpan={4}>{translate('base')}</th>
<th colSpan={5}>{translate('max')}</th>
<th className="lft" colSpan={7} />
<th className="lft" colSpan={5} />
<th className="lft" colSpan={8} />
</tr>
<tr>
<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('speed')}>
{translate('speed')}
</th>
<th className="sortable" onClick={sortShips('boost')}>
{translate('boost')}
</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')}> <th className='sortable lft' onClick={sortShips('topSpeed')}>{translate('speed')}</th>
{translate('speed')} <th className='sortable' onClick={sortShips('topBoost')}>{translate('boost')}</th>
</th> <th className='sortable' onClick={sortShips('maxJumpRange')}>{translate('jump')}</th>
<th className="sortable" onClick={sortShips('topBoost')}> <th className='sortable' onClick={sortShips('maxCargo')}>{translate('cargo')}</th>
{translate('boost')} <th className='sortable' onClick={sortShips('maxPassengers')}>{translate('passengers')}</th>
</th>
<th className="sortable" onClick={sortShips('maxJumpRange')}>
{translate('jump')}
</th>
<th className="sortable" onClick={sortShips('maxCargo')}>
{translate('cargo')}
</th>
<th className="sortable" onClick={sortShips('maxPassengers')} 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>
<th
className="sortable lft"
onClick={sortShips('retailCost')}
>
{units.CR}
</th>
<th className="sortable lft" onClick={sortShips('hullMass')}>
{units.T}
</th>
<th className="sortable lft" onClick={sortShips('speed')}>
{units['m/s']}
</th>
<th className="sortable" onClick={sortShips('boost')}>
{units['m/s']}
</th>
<th>&nbsp;</th>
<th
className="sortable"
onClick={sortShips('baseShieldStrength')}
>
{units.MJ}
</th>
<th className="sortable lft" onClick={sortShips('topSpeed')}>
{units['m/s']}
</th>
<th className="sortable" onClick={sortShips('topBoost')}>
{units['m/s']}
</th>
<th className="sortable" onClick={sortShips('maxJumpRange')}>
{units.LY}
</th>
<th className="sortable" onClick={sortShips('maxCargo')}>
{units.T}
</th>
<th>&nbsp;</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 distributor')}
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="sortable lft" onClick={sortShips('int', 0)}> <th className='lft' colSpan={6}>{translate('core module classes')}</th>
1 <th colSpan={5} className='sortable lft' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th>
</th> <th colSpan={8} className='sortable lft' onClick={sortShips('intCount')}>{translate('internal compartments')}</th>
<th className="sortable" onClick={sortShips('int', 1)}> </tr>
2 <tr>
</th> <th className='sortable lft' onClick={sortShips('retailCost')}>{units.CR}</th>
<th className="sortable" onClick={sortShips('int', 2)}> <th className='sortable lft' onClick={sortShips('hullMass')}>{units.T}</th>
3 <th className='sortable lft' onClick={sortShips('speed')}>{units['m/s']}</th>
</th> <th className='sortable' onClick={sortShips('boost')}>{units['m/s']}</th>
<th className="sortable" onClick={sortShips('int', 3)}> <th>&nbsp;</th>
4 <th className='sortable' onClick={sortShips('baseShieldStrength')}>{units.MJ}</th>
</th> <th className='sortable lft' onClick={sortShips('topSpeed')}>{units['m/s']}</th>
<th className="sortable" onClick={sortShips('int', 4)}> <th className='sortable' onClick={sortShips('topBoost')}>{units['m/s']}</th>
5 <th className='sortable' onClick={sortShips('maxJumpRange')}>{units.LY}</th>
</th> <th className='sortable' onClick={sortShips('maxCargo')}>{units.T}</th>
<th className="sortable" onClick={sortShips('int', 5)}> <th>&nbsp;</th>
6 <th className='sortable lft' onMouseEnter={termtip.bind(null, 'power plant')} onMouseLeave={hide} onClick={sortShips('standard', 0)}>{'pp'}</th>
</th> <th className='sortable' onMouseEnter={termtip.bind(null, 'thrusters')} onMouseLeave={hide} onClick={sortShips('standard', 1)}>{'th'}</th>
<th className="sortable" onClick={sortShips('int', 6)}> <th className='sortable' onMouseEnter={termtip.bind(null, 'frame shift drive')} onMouseLeave={hide} onClick={sortShips('standard', 2)}>{'fsd'}</th>
7 <th className='sortable' onMouseEnter={termtip.bind(null, 'life support')} onMouseLeave={hide} onClick={sortShips('standard', 3)}>{'ls'}</th>
</th> <th className='sortable' onMouseEnter={termtip.bind(null, 'power distriubtor')} onMouseLeave={hide} onClick={sortShips('standard', 4)}>{'pd'}</th>
<th className="sortable" onClick={sortShips('int', 7)}> <th className='sortable' onMouseEnter={termtip.bind(null, 'sensors')} onMouseLeave={hide} onClick={sortShips('standard', 5)}>{'s'}</th>
8
</th> <th className='sortable lft' onClick={sortShips('hp',1)}>{translate('S')}</th>
</tr> <th className='sortable' onClick={sortShips('hp', 2)}>{translate('M')}</th>
</thead> <th className='sortable' onClick={sortShips('hp', 3)}>{translate('L')}</th>
<tbody onMouseLeave={this._highlightShip.bind(this, null)}> <th className='sortable' onClick={sortShips('hp', 4)}>{translate('H')}</th>
{detailRows} <th className='sortable' onClick={sortShips('hp', 0)}>{translate('U')}</th>
</tbody>
</table> <th className='sortable lft' onClick={sortShips('int', 0)} >1</th>
<th className='sortable' onClick={sortShips('int', 1)} >2</th>
<th className='sortable' onClick={sortShips('int', 2)} >3</th>
<th className='sortable' onClick={sortShips('int', 3)} >4</th>
<th className='sortable' onClick={sortShips('int', 4)} >5</th>
<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 className="table-tools" >
<label><input type="checkbox" checked={this.state.groupCompared} onClick={() => this._toggleGroupCompared()}/>{translate('Group highlighted ships')}</label>
</div>
</div>
</div> </div>
); );
} }

View File

@@ -7,21 +7,11 @@ import Module from './Module';
* @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass * @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass
* @param {number} fuel Optional - The fuel consumed during the jump * @param {number} fuel Optional - The fuel consumed during the jump
* @return {number} Distance in Light Years * @return {number} Distance in Light Years
* @param {object} ship Ship instance
*/ */
export function jumpRange(mass, fsd, fuel, ship) { export function jumpRange(mass, fsd, fuel) {
const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel; const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass; const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
let jumpAddition = 0; return Math.pow(Math.min(fuel === undefined ? fsdMaxFuelPerJump : fuel, fsdMaxFuelPerJump) / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass;
if (ship) {
mass += ship.reserveFuelCapacity || 0;
for (const module of ship.internal) {
if (module && module.m && module.m.grp === 'gfsb' && ship.getSlotStatus(module) == 3) {
jumpAddition += module.m.getJumpBoost();
}
}
}
return (Math.pow(Math.min(fuel === undefined ? fsdMaxFuelPerJump : fuel, fsdMaxFuelPerJump) / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass) + jumpAddition;
} }
/** /**
@@ -31,9 +21,8 @@ export function jumpRange(mass, fsd, fuel, ship) {
* @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass * @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass
* @param {number} fuel The total fuel available * @param {number} fuel The total fuel available
* @return {number} Distance in Light Years * @return {number} Distance in Light Years
* @param {object} ship Ship instance
*/ */
export function totalJumpRange(mass, fsd, fuel, ship) { export function totalJumpRange(mass, fsd, fuel) {
const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel; const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass; const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
@@ -41,7 +30,7 @@ export function totalJumpRange(mass, fsd, fuel, ship) {
let totalRange = 0; let totalRange = 0;
while (fuelRemaining > 0) { while (fuelRemaining > 0) {
const fuelForThisJump = Math.min(fuelRemaining, fsdMaxFuelPerJump); const fuelForThisJump = Math.min(fuelRemaining, fsdMaxFuelPerJump);
totalRange += this.jumpRange(mass, fsd, fuelForThisJump, ship); totalRange += this.jumpRange(mass, fsd, fuelForThisJump);
// Mass is reduced // Mass is reduced
mass -= fuelForThisJump; mass -= fuelForThisJump;
fuelRemaining -= fuelForThisJump; fuelRemaining -= fuelForThisJump;
@@ -56,7 +45,7 @@ export function totalJumpRange(mass, fsd, fuel, ship) {
* @param {number} baseShield Base Shield strength MJ for ship * @param {number} baseShield Base Shield strength MJ for ship
* @param {object} sg The shield generator used * @param {object} sg The shield generator used
* @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any) * @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any)
* @return {number} Approximate shield strengh in MJ * @return {number} Approximate shield strengh in MJ
*/ */
export function shieldStrength(mass, baseShield, sg, multiplier) { export function shieldStrength(mass, baseShield, sg, multiplier) {
// sg might be a module or a template; handle either here // sg might be a module or a template; handle either here
@@ -66,12 +55,13 @@ export function shieldStrength(mass, baseShield, sg, multiplier) {
let minMul = sg instanceof Module ? sg.getMinMul() : sg.minmul; let minMul = sg instanceof Module ? sg.getMinMul() : sg.minmul;
let optMul = sg instanceof Module ? sg.getOptMul() : sg.optmul; let optMul = sg instanceof Module ? sg.getOptMul() : sg.optmul;
let maxMul = sg instanceof Module ? sg.getMaxMul() : sg.maxmul; let maxMul = sg instanceof Module ? sg.getMaxMul() : sg.maxmul;
let xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass)); let xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass));
let exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass))); let exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
let ynorm = Math.pow(xnorm, exponent); let ynorm = Math.pow(xnorm, exponent);
let mul = minMul + ynorm * (maxMul - minMul); let mul = minMul + ynorm * (maxMul - minMul);
return (baseShield * mul * multiplier); return baseShield * mul * multiplier;
} }
/** /**
@@ -97,16 +87,6 @@ export function speed(mass, baseSpeed, thrusters, engpip) {
return results; return results;
} }
/**
* Calculate pip multiplier for speed.
* @param {number} baseSpeed The base speed of ship in data
* @param {number} topSpeed The top speed of ship in data
* @return {number} The multiplier that pips affect speed.
*/
export function calcPipSpeed(baseSpeed, topSpeed) {
return (topSpeed - baseSpeed) / (4 * topSpeed);
}
/** /**
* Calculate pitch of a ship based on mass and thrusters * Calculate pitch of a ship based on mass and thrusters
* @param {number} mass the mass of the ship * @param {number} mass the mass of the ship
@@ -222,7 +202,7 @@ function calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, base
* Calculate speed for a given setup * Calculate speed for a given setup
* @param {number} mass the mass of the ship * @param {number} mass the mass of the ship
* @param {number} baseSpeed the base speed of the ship * @param {number} baseSpeed the base speed of the ship
* @param {object} thrusters the thrusters of the ship * @param {ojbect} thrusters the thrusters of the ship
* @param {number} engpip the multiplier per pip to engines * @param {number} engpip the multiplier per pip to engines
* @param {number} eng the pips to engines * @param {number} eng the pips to engines
* @param {number} boostFactor the boost factor for ths ship * @param {number} boostFactor the boost factor for ths ship
@@ -250,7 +230,7 @@ export function calcSpeed(mass, baseSpeed, thrusters, engpip, eng, boostFactor,
* Calculate pitch for a given setup * Calculate pitch for a given setup
* @param {number} mass the mass of the ship * @param {number} mass the mass of the ship
* @param {number} basePitch the base pitch of the ship * @param {number} basePitch the base pitch of the ship
* @param {object} thrusters the thrusters of the ship * @param {ojbect} thrusters the thrusters of the ship
* @param {number} engpip the multiplier per pip to engines * @param {number} engpip the multiplier per pip to engines
* @param {number} eng the pips to engines * @param {number} eng the pips to engines
* @param {number} boostFactor the boost factor for ths ship * @param {number} boostFactor the boost factor for ths ship
@@ -345,6 +325,7 @@ export function shieldMetrics(ship, sys) {
const shieldGeneratorSlot = ship.findInternalByGroup('sg'); const shieldGeneratorSlot = ship.findInternalByGroup('sg');
if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) { if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) {
const shieldGenerator = shieldGeneratorSlot.m; const shieldGenerator = shieldGeneratorSlot.m;
// Boosters // Boosters
let boost = 1; let boost = 1;
let boosterExplDmg = 1; let boosterExplDmg = 1;
@@ -358,33 +339,29 @@ export function shieldMetrics(ship, sys) {
boosterThermDmg = boosterThermDmg * (1 - slot.m.getThermalResistance()); boosterThermDmg = boosterThermDmg * (1 - slot.m.getThermalResistance());
} }
} }
// Calculate diminishing returns for boosters // Calculate diminishing returns for boosters
// Diminishing returns not currently in-game // Diminishing returns not currently in-game
// boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5); // boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5);
// Remove base shield generator strength // Remove base shield generator strength
boost -= 1; boost -= 1;
// Apply diminishing returns
boosterExplDmg = boosterExplDmg > 0.7 ? boosterExplDmg : 0.7 - (0.7 - boosterExplDmg) / 2;
boosterKinDmg = boosterKinDmg > 0.7 ? boosterKinDmg : 0.7 - (0.7 - boosterKinDmg) / 2;
boosterThermDmg = boosterThermDmg > 0.7 ? boosterThermDmg : 0.7 - (0.7 - boosterThermDmg) / 2;
let shieldAddition = 0; const generatorStrength = this.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1);
if (ship) {
for (const module of ship.internal) {
if (module && module.m && module.m.grp === 'gsrp' && module.enabled) {
shieldAddition += module.m.getShieldAddition();
}
}
}
let generatorStrength = this.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1);
const boostersStrength = generatorStrength * boost; const boostersStrength = generatorStrength * boost;
// Recover time is the time taken to go from 0 to 50%. It includes a 16-second wait before shields start to recover // Recover time is the time taken to go from 0 to 50%. It includes a 16-second wait before shields start to recover
const shieldToRecover = (generatorStrength + boostersStrength + shieldAddition) / 2; const shieldToRecover = (generatorStrength + boostersStrength) / 2;
const powerDistributor = ship.standard[4].m; const powerDistributor = ship.standard[4].m;
const sysRechargeRate = this.sysRechargeRate(powerDistributor, sys); const sysRechargeRate = this.sysRechargeRate(powerDistributor, sys);
// Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes // Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes
// 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration // 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
let capacitorDrain = (shieldGenerator.getBrokenRegenerationRate() * shieldGenerator.getDistDraw()) - sysRechargeRate; let capacitorDrain = (shieldGenerator.getBrokenRegenerationRate() * 0.6) - sysRechargeRate;
let capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain; let capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
let recover = 16; let recover = 16;
@@ -397,19 +374,19 @@ export function shieldMetrics(ship, sys) {
const remainingShieldToRecover = shieldToRecover - capacitorLifetime * shieldGenerator.getBrokenRegenerationRate(); const remainingShieldToRecover = shieldToRecover - capacitorLifetime * shieldGenerator.getBrokenRegenerationRate();
if (sys === 0) { if (sys === 0) {
// No system pips so will never recover shields // No system pips so will never recover shields
recover = Math.Infinity; recover = Math.Inf;
} else { } else {
// Recover remaining shields at the rate of the power distributor's recharge // Recover remaining shields at the rate of the power distributor's recharge
recover += remainingShieldToRecover / (sysRechargeRate / shieldGenerator.getDistDraw()); recover += remainingShieldToRecover / (sysRechargeRate / 0.6);
} }
} }
// Recharge time is the time taken to go from 50% to 100% // Recharge time is the time taken to go from 50% to 100%
const shieldToRecharge = (generatorStrength + boostersStrength + shieldAddition) / 2; const shieldToRecharge = (generatorStrength + boostersStrength) / 2;
// Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes // Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes
// 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration // 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
capacitorDrain = (shieldGenerator.getRegenerationRate() * shieldGenerator.getDistDraw()) - sysRechargeRate; capacitorDrain = (shieldGenerator.getRegenerationRate() * 0.6) - sysRechargeRate;
capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain; capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
let recharge = 0; let recharge = 0;
@@ -425,17 +402,15 @@ export function shieldMetrics(ship, sys) {
recharge = Math.Inf; recharge = Math.Inf;
} else { } else {
// Recharge remaining shields at the rate of the power distributor's recharge // Recharge remaining shields at the rate of the power distributor's recharge
recharge += remainingShieldToRecharge / (sysRechargeRate / shieldGenerator.getDistDraw()); recharge += remainingShieldToRecharge / (sysRechargeRate / 0.6);
} }
} }
shield = { shield = {
generator: generatorStrength, generator: generatorStrength,
boosters: boostersStrength, boosters: boostersStrength,
addition: shieldAddition,
cells: ship.shieldCells, cells: ship.shieldCells,
summary: generatorStrength + boostersStrength + shieldAddition, total: generatorStrength + boostersStrength + ship.shieldCells,
total: generatorStrength + boostersStrength + ship.shieldCells + shieldAddition,
recover, recover,
recharge, recharge,
}; };
@@ -450,74 +425,34 @@ export function shieldMetrics(ship, sys) {
max: 1 - maxSysResistance max: 1 - maxSysResistance
}; };
/**
* An object that stores a selection of difference damage multipliers that
* deal with a ship's shield strength.
* @typedef {Object} ShieldDamageMults
* @property {number} generator Base damage multiplier of the shield
* contributing it's base resistance.
* @property {number} boosters Damage multiplier contributed by all
* boosters, i.e. `rawMj / (generator * boosters)` equals shield strength
* with 0 pips to sys.
* @property {number} sys Damage multiplier contributed by pips to sys.
* @property {number} base Damage multiplier with 0 pips to sys; just
* boosters and shield generator. Equals `generator * boosters`.
* @property {number} total Damage multiplier with current pip settings.
* @property {number} max Damage multiplier with 4 pips to sys.
*/
let sgExplosiveDmg = 1 - shieldGenerator.getExplosiveResistance();
let sgSbExplosiveDmg = diminishingReturnsShields(sgExplosiveDmg, sgExplosiveDmg * boosterExplDmg);
/** @type {ShieldDamageMults} */
shield.explosive = { shield.explosive = {
generator: sgExplosiveDmg, generator: 1 - shieldGenerator.getExplosiveResistance(),
boosters: sgSbExplosiveDmg / sgExplosiveDmg, boosters: boosterExplDmg,
sys: (1 - sysResistance), sys: (1 - sysResistance),
base: sgSbExplosiveDmg, total: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - sysResistance),
total: sgSbExplosiveDmg * (1 - sysResistance), max: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - maxSysResistance)
max: sgSbExplosiveDmg * (1 - maxSysResistance),
}; };
let sgKineticDmg = 1 - shieldGenerator.getKineticResistance();
let sgSbKineticDmg = diminishingReturnsShields(sgKineticDmg, sgKineticDmg * boosterKinDmg);
/** @type {ShieldDamageMults} */
shield.kinetic = { shield.kinetic = {
generator: sgKineticDmg, generator: 1 - shieldGenerator.getKineticResistance(),
boosters: sgSbKineticDmg / sgKineticDmg, boosters: boosterKinDmg,
sys: (1 - sysResistance), sys: (1 - sysResistance),
base: sgSbKineticDmg, total: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - sysResistance),
total: sgSbKineticDmg * (1 - sysResistance), max: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - maxSysResistance)
max: sgSbKineticDmg * (1 - maxSysResistance),
}; };
let sgThermalDmg = 1 - shieldGenerator.getThermalResistance();
let sgSbThermalDmg = diminishingReturnsShields(sgThermalDmg , sgThermalDmg * boosterThermDmg);
/** @type {ShieldDamageMults} */
shield.thermal = { shield.thermal = {
generator: sgThermalDmg, generator: 1 - shieldGenerator.getThermalResistance(),
boosters: sgSbThermalDmg / sgThermalDmg, boosters: boosterThermDmg,
sys: (1 - sysResistance), sys: (1 - sysResistance),
base: sgSbThermalDmg, total: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - sysResistance),
total: sgSbThermalDmg * (1 - sysResistance), max: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - maxSysResistance)
max: sgSbThermalDmg * (1 - maxSysResistance),
}; };
} }
return shield; return shield;
} }
/**
* Calculate time from one boost to another
* @return {number} Boost frequency in seconds
* @param {Ship} ship Ship object
*/
export function calcBoost(ship) {
if (!ship.boostEnergy || !ship.standard[4] || !ship.standard[4].m) {
return undefined;
}
return ship.boostEnergy / ship.standard[4].m.getEnginesRechargeRate();
}
/** /**
* Calculate armour metrics * Calculate armour metrics
* @param {Object} ship The ship * @param {Object} ship The ship
@@ -530,29 +465,34 @@ export function armourMetrics(ship) {
let moduleArmour = 0; let moduleArmour = 0;
let moduleProtection = 1; let moduleProtection = 1;
const bulkheads = ship.bulkheads.m;
let hullExplDmgs = []; let hullExplDmg = 1;
let hullKinDmgs = []; let hullKinDmg = 1;
let hullThermDmgs = []; let hullThermDmg = 1;
let hullCausDmgs = [];
// 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.enabled && (slot.m.grp === 'hr' || slot.m.grp === 'ghrp' || slot.m.grp == 'mahr')) { if (slot.m && slot.m.grp == 'hr') {
armourReinforcement += slot.m.getHullReinforcement(); armourReinforcement += slot.m.getHullReinforcement();
// Hull boost for HRPs is applied against the ship's base armour // Hull boost for HRPs is applied against the ship's base armour
armourReinforcement += ship.baseArmour * slot.m.getModValue('hullboost') / 10000; armourReinforcement += ship.baseArmour * slot.m.getModValue('hullboost') / 10000;
hullExplDmgs.push(1 - slot.m.getExplosiveResistance());
hullKinDmgs.push(1 - slot.m.getKineticResistance()); hullExplDmg = hullExplDmg * (1 - slot.m.getExplosiveResistance());
hullThermDmgs.push(1 - slot.m.getThermalResistance()); hullKinDmg = hullKinDmg * (1 - slot.m.getKineticResistance());
hullCausDmgs.push(1 - slot.m.getCausticResistance()); hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance());
} }
if (slot.m && slot.enabled && (slot.m.grp == 'mrp' || slot.m.grp == 'gmrp')) { if (slot.m && slot.m.grp == 'mrp') {
moduleArmour += slot.m.getIntegrity(); moduleArmour += slot.m.getIntegrity();
moduleProtection = moduleProtection * (1 - slot.m.getProtection()); moduleProtection = moduleProtection * (1 - slot.m.getProtection());
} }
} }
moduleProtection = 1 - moduleProtection; moduleProtection = 1 - moduleProtection;
// Apply diminishing returns
hullExplDmg = hullExplDmg > 0.7 ? hullExplDmg : 0.7 - (0.7 - hullExplDmg) / 2;
hullKinDmg = hullKinDmg > 0.7 ? hullKinDmg : 0.7 - (0.7 - hullKinDmg) / 2;
hullThermDmg = hullThermDmg > 0.7 ? hullThermDmg : 0.7 - (0.7 - hullThermDmg) / 2;
const armour = { const armour = {
bulkheads: armourBulkheads, bulkheads: armourBulkheads,
reinforcement: armourReinforcement, reinforcement: armourReinforcement,
@@ -569,41 +509,24 @@ export function armourMetrics(ship) {
total: 1 total: 1
}; };
let armourExplDmg = 1 - ship.bulkheads.m.getExplosiveResistance();
let armourReinforcedExplDmg = diminishingReturnsArmour(armourExplDmg, ...hullExplDmgs);
armour.explosive = { armour.explosive = {
bulkheads: armourExplDmg, bulkheads: 1 - ship.bulkheads.m.getExplosiveResistance(),
reinforcement: armourReinforcedExplDmg / armourExplDmg, reinforcement: hullExplDmg,
total: armourReinforcedExplDmg, total: (1 - ship.bulkheads.m.getExplosiveResistance()) * hullExplDmg
res: 1 - armourReinforcedExplDmg
}; };
let armourKinDmg = 1 - ship.bulkheads.m.getKineticResistance();
let armourReinforcedKinDmg = diminishingReturnsArmour(armourKinDmg, ...hullKinDmgs);
armour.kinetic = { armour.kinetic = {
bulkheads: armourKinDmg, bulkheads: 1 - ship.bulkheads.m.getKineticResistance(),
reinforcement: armourReinforcedKinDmg / armourKinDmg, reinforcement: hullKinDmg,
total: armourReinforcedKinDmg, total: (1 - ship.bulkheads.m.getKineticResistance()) * hullKinDmg
res: 1 - armourReinforcedKinDmg
}; };
let armourThermDmg = 1 - ship.bulkheads.m.getThermalResistance();
let armourReinforcedThermDmg = diminishingReturnsArmour(armourThermDmg, ...hullThermDmgs);
armour.thermal = { armour.thermal = {
bulkheads: armourThermDmg, bulkheads: 1 - ship.bulkheads.m.getThermalResistance(),
reinforcement: armourReinforcedThermDmg / armourThermDmg, reinforcement: hullThermDmg,
total: armourReinforcedThermDmg, total: (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg
res: 1 - armourReinforcedThermDmg
}; };
let armourCausDmg = 1 - ship.bulkheads.m.getCausticResistance();
let armourReinforcedCausDmg = diminishingReturnsArmour(armourCausDmg, ...hullCausDmgs);
armour.caustic = {
bulkheads: armourCausDmg,
reinforcement: armourReinforcedCausDmg / armourCausDmg,
total: armourReinforcedCausDmg,
res: 1 - armourReinforcedCausDmg,
};
return armour; return armour;
} }
@@ -775,55 +698,20 @@ export function _sustainedDps(ship, opponent, opponentShields, opponentArmour, e
return { shieldsdps, armoursdps, eps }; return { shieldsdps, armoursdps, eps };
} }
/**
* Stores SDPS split up by type.
* @typedef {Object} SDps
* @property {number} absolute Damage of type absolute
* @property {number} explosive Damage of type explosive
* @property {number} kinetic Damage of type kinetic
* @property {number} thermal Damage of type thermal
* @property {number} [total] Sum of all damage types
*/
/**
* An object that holds information about SDPS for a given weapon and opponent.
* @typedef {Object} WeaponDamage
* @property {number} eps Energy per second
* @property {Object} damage An object that stores damage inflicted by
* the weapon.
* @property {Object} effectiveness An object that stores the effectiveness of
* the weapon against the opponent given.
*/
/**
* Stores overall SDPS and against a given opponent's shields and armour.
* @typedef {Object} WeaponDamage~damage
* @property {SDps} base Overall SDPS.
* @property {SDps} shields SDPS against the given opponent's shields.
* @property {SDps} armour SDPS against the given opponent's armour.
*/
/** /**
* Calculate the sustained DPS for a weapon at a given range * Calculate the sustained DPS for a weapon at a given range
* @param {Object} m The weapon * @param {Object} m The weapon
* @param {Object} opponent The opponent ship * @param {Object} opponent The opponent ship
* @param {Object} opponentShields The opponent's shield resistances * @param {Object} opponentShields The opponent's shield resistances
* @param {Object} opponentArmour The opponent's armour resistances * @param {Object} opponentArmour The opponent's armour resistances
* @param {int} engagementrange The range between the ship and opponent * @param {int} engagementrange The range between the ship and opponent
* @returns {WeaponDamage} Sustained DPS for shield and armour * @returns {Object} Sustained DPS for shield and armour
*/ */
export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange) { export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange) {
const opponentHasShields = opponentShields.generator ? true : false; const opponentHasShields = opponentShields.generator ? true : false;
const weapon = { const weapon = {
eps: 0, eps: 0,
damage: { damage: {
base: {
absolute: 0,
explosive: 0,
kinetic: 0,
thermal: 0,
total: 0,
},
shields: { shields: {
absolute: 0, absolute: 0,
explosive: 0, explosive: 0,
@@ -843,14 +731,12 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
shields: { shields: {
range: 1, range: 1,
sys: opponentHasShields ? opponentShields.absolute.sys : 1, sys: opponentHasShields ? opponentShields.absolute.sys : 1,
resistance: 1, resistance: 1
dpe: 1
}, },
armour: { armour: {
range: 1, range: 1,
hardness: 1, hardness: 1,
resistance: 1, resistance: 1
dpe: 1
} }
} }
}; };
@@ -859,7 +745,7 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
weapon.eps = m.getClip() ? (m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getEps(); weapon.eps = m.getClip() ? (m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getEps();
// Initial sustained DPS // Initial sustained DPS
let sDps = m.getSDps(); let sDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps();
// Take fall-off in to account // Take fall-off in to account
const falloff = m.getFalloff(); const falloff = m.getFalloff();
@@ -870,12 +756,6 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
sDps *= dropoff; sDps *= dropoff;
} }
weapon.damage.base.absolute = sDps * m.getDamageDist().A;
weapon.damage.base.explosive = sDps * m.getDamageDist().E;
weapon.damage.base.kinetic = sDps * m.getDamageDist().K;
weapon.damage.base.thermal = sDps * m.getDamageDist().T;
weapon.damage.base.total = sDps;
// Piercing/hardness modifier (for armour only) // Piercing/hardness modifier (for armour only)
const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness; const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness;
weapon.effectiveness.armour.hardness = armourMultiple; weapon.effectiveness.armour.hardness = armourMultiple;
@@ -910,19 +790,11 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
weapon.damage.shields.total = weapon.damage.shields.absolute + weapon.damage.shields.explosive + weapon.damage.shields.kinetic + weapon.damage.shields.thermal; weapon.damage.shields.total = weapon.damage.shields.absolute + weapon.damage.shields.explosive + weapon.damage.shields.kinetic + weapon.damage.shields.thermal;
weapon.damage.armour.total = weapon.damage.armour.absolute + weapon.damage.armour.explosive + weapon.damage.armour.kinetic + weapon.damage.armour.thermal; weapon.damage.armour.total = weapon.damage.armour.absolute + weapon.damage.armour.explosive + weapon.damage.armour.kinetic + weapon.damage.armour.thermal;
weapon.effectiveness.shields.resistance *= shieldsResistance; weapon.effectiveness.shields.resistance *= shieldsResistance;
weapon.effectiveness.armour.resistance *= armourResistance; weapon.effectiveness.armour.resistance *= armourResistance;
weapon.effectiveness.shields.total = weapon.effectiveness.shields.range * weapon.effectiveness.shields.sys * weapon.effectiveness.shields.resistance; weapon.effectiveness.shields.total = weapon.effectiveness.shields.range * weapon.effectiveness.shields.sys * weapon.effectiveness.shields.resistance;
weapon.effectiveness.armour.total = weapon.effectiveness.armour.range * weapon.effectiveness.armour.resistance * weapon.effectiveness.armour.hardness; weapon.effectiveness.armour.total = weapon.effectiveness.armour.range * weapon.effectiveness.armour.resistance * weapon.effectiveness.armour.hardness;
weapon.effectiveness.shields.dpe = weapon.damage.shields.total / m.getEps() / m.getSustainedFactor();
weapon.effectiveness.armour.dpe = weapon.damage.armour.total / m.getEps() / m.getSustainedFactor();
return weapon; return weapon;
} }
@@ -964,10 +836,7 @@ 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 there is nothing to remove, we're don instantly if (drainPerSecond <= 0) {
if (!amount) {
return 0;
} if (drainPerSecond <= 0) {
// Simple result // Simple result
return amount / dps; return amount / dps;
} else { } else {
@@ -984,52 +853,3 @@ export function timeToDeplete(amount, dps, eps, capacity, recharge) {
} }
} }
} }
/**
* Checks whether diminishing returns should be applied to shield damage
* multipliers and does so if necessary.
* @param {number} shieldMult Damage multiplier of shield generator
* @param {number} combinedMult Damage multiplier of shields and shield boosters
* @returns {number} Overall damage multiplier
*/
export function diminishingReturnsShields(shieldMult, combinedMult) {
let max = shieldMult * 0.7;
if (combinedMult < max) {
return mapIntoDiminishingRange(max / 2, max, combinedMult);
} else {
return combinedMult;
}
}
/**
* Checks whether diminishing returns should be applied to armour damage
* multipliers and does so if necessary.
* @param {...any} mults Damage multipliers of alloys and hull reinforcement
* packages
* @returns {number} Overall damage multiplier
*/
export function diminishingReturnsArmour(...mults) {
let max = Math.min(0.7, ...mults);
let combined = mults.reduce((aggr, v) => aggr * v);
let diminished = mapIntoDiminishingRange(0.35, max, combined);
if (diminished < 0.7) {
return diminished;
} else {
return combined;
}
}
/**
* Applies diminishing returns to a damage multiplier. Effictively, the range
* [`0`, `max`]` is mapped into the range [`min`, `max`] for the value `now`.
* It can also happen, that `now` is outside of the range [`min`, `max`], then
* `now` is actually improved, i.e. enlarged.
* @param {number} min Best theoretical damage multiplier
* @param {number} max Damage multiplier from which diminishing returns start to
* be applied
* @param {number} now The current damage multiplier
* @returns {number} Remapped damage multiplier
*/
export function mapIntoDiminishingRange(min, max, now) {
return min + (max - min) * (now / max);
}

View File

@@ -9,16 +9,12 @@ export const StandardArray = [
'pd', // Power Distributor 'pd', // Power Distributor
's', // Sensors 's', // Sensors
'ft', // Fuel Tank 'ft', // Fuel Tank
'gpp', // Guardian Hybrid Power Plant
'gpd' // Guardian Hybrid Power Distributor
]; ];
// Map to lookup group labels/names for component grp, used for JSON Serialization // Map to lookup group labels/names for component grp, used for JSON Serialization
export const ModuleGroupToName = { export const ModuleGroupToName = {
// Standard // Standard
pp: 'Power Plant', pp: 'Power Plant',
gpp: 'Guardian Hybrid Power Plant',
gpd: 'Guardian Power Distributor',
t: 'Thrusters', t: 'Thrusters',
fsd: 'Frame Shift Drive', fsd: 'Frame Shift Drive',
ls: 'Life Support', ls: 'Life Support',
@@ -52,14 +48,6 @@ export const ModuleGroupToName = {
pcq: 'Luxury Passenger Cabin', pcq: 'Luxury Passenger Cabin',
cc: 'Collector Limpet Controller', cc: 'Collector Limpet Controller',
ss: 'Surface Scanner', ss: 'Surface Scanner',
gsrp: 'Guardian Shield Reinforcement Packages',
gfsb: 'Guardian Frame Shift Drive Booster',
ghrp: 'Guardian Hull Reinforcement Package',
gmrp: 'Guardian Module Reinforcement Package',
mahr: 'Meta Alloy Hull Reinforcement Package',
sua: 'Supercruise Assist',
mlc: "Multi Limpet Controller",
rpl: "Repair Limpet Controller",
// Hard Points // Hard Points
bl: 'Beam Laser', bl: 'Beam Laser',
@@ -87,20 +75,7 @@ export const ModuleGroupToName = {
sb: 'Shield Booster', sb: 'Shield Booster',
tp: 'Torpedo Pylon', tp: 'Torpedo Pylon',
sfn: 'Shutdown Field Neutraliser', sfn: 'Shutdown Field Neutraliser',
xs: 'Xeno Scanner', xs: 'Xeno Scanner'
rcpl: 'Recon Limpet Controller',
rsl: 'Research Limpet Controller',
dtl: 'Decontamination Limpet Controller',
gpc: 'Guardian Plasma Charger',
ggc: 'Guardian Gauss Cannon',
tbsc: 'Shock Cannon',
gsc: 'Guardian Shard Cannon',
tbem: 'Enzyme Missile Rack',
tbrfl: 'Remote Release Flechette Launcher',
pwa: 'Pulse Wave Analyser',
abl: 'Abrasion Blaster',
scl: 'Seismic Charge Launcher',
sdm: 'Sub-Surface Displacement Missile',
}; };
let GrpNameToCodeMap = {}; let GrpNameToCodeMap = {};
@@ -209,7 +184,7 @@ export const ShipFacets = [
i: 9 i: 9
}, },
{ // 10 { // 10
title: 'farthest range', title: 'fastest range',
props: ['unladenFastestRange', 'ladenFastestRange'], props: ['unladenFastestRange', 'ladenFastestRange'],
lbls: ['unladen', 'laden'], lbls: ['unladen', 'laden'],
unit: 'LY', unit: 'LY',

View File

@@ -2,6 +2,7 @@
* 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
@@ -10,4 +11,5 @@ export default class Modification {
this.id = id; this.id = id;
this.value = value; this.value = value;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -13,23 +13,11 @@ function filter(arr, maxClass, minClass, mass) {
return arr.filter(m => m.class <= maxClass && m.class >= minClass && (m.maxmass === undefined || mass <= m.maxmass)); return arr.filter(m => m.class <= maxClass && m.class >= minClass && (m.maxmass === undefined || mass <= m.maxmass));
} }
/**
* Filter SCO Modules to only return legal size.
* @param {Array} arr Array of available FSD modules.
* @param {number} maxSize Maximum allowable size for SCO modules.
* @return {Array} Subset of modules filtered based on legal size amd type.
*/
function sco_filter(arr, maxSize) {
return arr.filter(module => {
return !(module.hasOwnProperty('name') && module['name'] === "Frame Shift Drive (SCO)" && module['class'] < maxSize);
});
}
/** /**
* 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
@@ -54,7 +42,6 @@ export default class ModuleSet {
this.standard[0] = filter(stnd.pp, maxStandardArr[0], 0, mass); // Power Plant this.standard[0] = filter(stnd.pp, maxStandardArr[0], 0, mass); // Power Plant
this.standard[2] = filter(stnd.fsd, maxStandardArr[2], 0, mass); // FSD this.standard[2] = filter(stnd.fsd, maxStandardArr[2], 0, mass); // FSD
this.standard[2] = sco_filter(this.standard[2], maxStandardArr[2]) // FSD - Filter SCO Modules
this.standard[4] = filter(stnd.pd, maxStandardArr[4], 0, mass); // Power Distributor this.standard[4] = filter(stnd.pd, maxStandardArr[4], 0, mass); // Power Distributor
this.standard[6] = filter(stnd.ft, maxStandardArr[6], 0, mass); // Fuel Tank this.standard[6] = filter(stnd.ft, maxStandardArr[6], 0, mass); // Fuel Tank
// Thrusters, filter modules by class only (to show full list of ratings for that class) // Thrusters, filter modules by class only (to show full list of ratings for that class)

View File

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

View File

@@ -85,12 +85,12 @@ export function toDetailedBuild(buildName, ship) {
code = ship.toString(); code = ship.toString();
let data = { let data = {
$schema: 'https://coriolis.io/schemas/ship-loadout/4.json#', $schema: 'https://coriolis.edcd.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.io' + outfitURL(ship.id, code, buildName), url: 'https://coriolis.edcd.io' + outfitURL(ship.id, code, buildName),
code, code,
shipId: ship.id shipId: ship.id
}], }],

Some files were not shown because too many files have changed in this diff Show More