Compare commits

...

213 Commits

Author SHA1 Message Date
David Sangrey
436a50cb45 Merge pull request #765 from alex-williams/ISSUE_764_Unknown_Modules_Are_Selectable
Issue 764 unknown modules are selectable
2024-06-03 11:04:25 -04:00
David Sangrey
f7cf39a9ae Merge branch 'develop' into ISSUE_764_Unknown_Modules_Are_Selectable 2024-06-03 11:04:14 -04:00
David Sangrey
680f3b10f3 Merge pull request #762 from alex-williams/issue_600_add_advanced_weapons
Added 'special' field to certain modules to allow for clearer …
2024-06-03 11:02:17 -04:00
Alex Williams
b31de9c37a Merge pull request #763 from leonardofelin/patch-2
Update PT-BR translations
2024-06-02 20:17:06 +01:00
Alex Williams
9ef054c271 Fixed 'Missing Module' category showing up in Optional Selection drop-down and fixed 'Missing Power Plant', 'Missing Power Distributor' and 'Missing Frameshift Drive' showing up in the Selection drop-downs for those module slots. 2024-06-02 20:03:12 +01:00
leonardofelin
aa620be113 Update PT-BR translations
Added translated strings for coriolis-data PRs 106 & 107
2024-05-27 19:45:44 -03:00
Alex Williams
27f19a72a6 Merge branch 'alpha' into issue_600_add_advanced_weapons 2024-05-27 17:02:42 +01:00
Alex Williams
634be1f197 Added 'special' field to certain modules to allow for clearer appearance in search results that they are the special type of module. Updated English descriptions of Advanced Modules and Special Modules 2024-05-27 15:50:32 +01:00
Felix Linker
f747b25f26 Merge pull request #759 from alex-williams/Issue_754_Imports_need_to_be_more_graceful
Adds valid module checking to all types of modules on import
2024-05-27 08:49:11 +02:00
Felix Linker
02bf133c98 Merge branch 'develop' into Issue_754_Imports_need_to_be_more_graceful 2024-05-27 08:48:49 +02:00
Alex Williams
6c34a26273 Issue 754 imports need to be more graceful (#5)
* Adds valid module checking to all types of modules on import

* Changes as per comments on the PR
2024-05-24 18:23:04 +01:00
Alex Williams
b0b5c82131 Merge branch 'alpha' into Issue_754_Imports_need_to_be_more_graceful 2024-05-24 18:22:53 +01:00
Alex Williams
ee92f2f2e4 Adds the Advanced MC's, AX MC's, AX MR's and Nanite Torpedo (#4) 2024-05-24 17:56:20 +01:00
Alex Williams
0d749202e2 Changing to clone single branch for deployment, not the whole repo 2024-05-24 17:41:27 +01:00
Alex Williams
4283b0b839 Changed deployment ordering 2024-05-24 17:38:47 +01:00
Alex Williams
fbd9c3d282 Improving workflow 2024-05-24 16:23:08 +01:00
Alex Williams
cd68199a41 Adding workflow for autodeploy 2024-05-24 16:23:08 +01:00
Alex Williams
f885fde04f Fix changed files issue (#3)
* Copied de.js contents to new file de-fix.js

* Copied de.js contents back from de-fix.js

* Copied contents of ko.js to ko-fix.js

* Copied ko.js contents back from ko-fix.js

* Copied contents from BlueprintFunctions.js to BlueprintFunctions-fix.js

* Copied contents back from BlueprintFunctions-fix.js to BlueprintFunctions.js

* Copied contents of LineChart.jsx to LineChart-fix.jsx

* Copied contents back from LineChart-fix.jsx to LineChart.jsx

* Copied contents of PieChart.jsx to PieChart-fix.jsx

* Copied contents back from PieChart-fix.jsx to PieChart.jsx

* Copied contents from Slider.jsx to Slider-fix.jsx

* Copied contents back from Slider-fix.jsx to Slider.jsx

* Copied contents from VerticalBarChart.jsx to VerticalBarChart-fix.jsx

* Copied contents back from VerticalBarChart-fix.jsx to VerticalBarChart.jsx

* Deleting 'fix' files
2024-05-24 16:03:03 +01:00
David Sangrey
8f5375f732 Merge pull request #760 from alex-williams/Issue_758_adding_ax_nanite_torps_and_enhanced_missile_racks
Adds the Advanced MC's, AX MC's, AX MR's and Nanite Torpedo
2024-05-20 12:49:53 -04:00
Alex Williams
5c8ff57d16 Changes as per comments on the PR 2024-05-17 15:24:29 +01:00
Alex Williams
3156b6a533 Adds the Advanced MC's, AX MC's, AX MR's and Nanite Torpedo 2024-05-17 08:45:15 +01:00
Alex Williams
14ffa26ef9 Adds valid module checking to all types of modules on import 2024-05-16 19:23:33 +01:00
Alex Williams
18d7ada65f Merge pull request #755 from leonardofelin/pt-br_translations
Updated PT-BR translation with Planetary Approach Suite
2024-05-14 17:18:40 +01:00
leonardofelin
8593e18de4 Updated PT-BR translation with Planetary Approach Suite 2024-05-14 13:13:56 -03:00
leonardofelin
284b0b3ce2 Update pt.json - Brazilian Portuguese translations (#752)
* Update pt.json

Update Brazilian Portuguese translations:
- Updated Modules
- Engineering & Experimental Effect
- Corrections

* Update Portuguese Brazilian

Fixed Tab/Spaces indentation
2024-05-14 13:25:51 +02:00
David Sangrey
c3cb2cfa3b Add SCO Module Check (#748)
* Add SCO Module Check

Goes hand-in-hand with https://github.com/EDCD/coriolis-data/pull/98

* [748] Updated Filter Function

This time, readable!
2024-05-12 13:51:17 +02:00
Alex Williams
5d54eb8862 Adding python_nx to SHIP_FD_NAME_TO_CORIOLIS_NAME to fix import issue with Python Mk II 2024-05-12 13:48:28 +02:00
Felix Linker
9fc6508be4 coriolis-data specified as relative dependency again 2024-04-29 08:24:34 +02:00
Aleksandr
ef82cf4a00 [744] add support of experimental weapon stabilizer (#745) 2024-04-29 08:23:20 +02:00
Felix Linker
9766f78e21 Move eddb.io links to inara.cz 2023-04-07 19:25:16 +01:00
Felix Linker
6d8bd6ca44 Dependency update 2023-04-07 19:18:28 +01:00
Sam Clayton
875af31ffe Update to Webpack 5 (fixes crypto error on build) (#738)
* Updating react-number-editor dependency from stale named branch

* Remove references to deprecated react-addons-perf package

* Issue #25 Webpack updated to current version, many
dependencies updated, Babel & Webpack configs updated.
Add dev & prod Dockerfiles and update README with Docker instructions
Created webpack.common.js.
Coriolis-data now specified as github dependency

* Bump bugfix versions of react & react-dom only

* Workbox dependency upgrade for webpack 5 compat

* Stab at upgrading workbox dep
Far more fatal webpack errors :(

* Automate reinstall/rebuild with npm script

* Working build again w updated deps
Disabled/commented out all bugsnag references
Added production-like Docker build for troubleshooting issues that don't
 appear in dev server

* Remove deprecated @babel/polyfill import & dependency

* Fix to service worker to v5 of workbox
and align with webpack 5 plugin

* Disabling recent round of polyfills. Don't think
they're necessary.

* Whitespace in package.json

* Add Buffer as Webpack plugin. Fix indenting.
Fix deprecated call to Buffer.

* Remove bugsnag and deprecated babel code that was
 commented out, per convo with Felix

---------

Co-authored-by: Sam Clayton <sam@goranku.com>
2023-03-01 21:55:23 +01:00
Felix Linker
93adcb3daf Merge pull request #730 from Sid127/edomh-v1
Initial EDOMH integration
2022-12-27 13:19:05 +01:00
Felix Linker
ec9a07b143 Merge pull request #732 from EDCD/dependabot/npm_and_yarn/decode-uri-component-0.2.2
Bump decode-uri-component from 0.2.0 to 0.2.2
2022-12-26 13:37:46 +01:00
dependabot[bot]
bb8eeb4d3f Bump decode-uri-component from 0.2.0 to 0.2.2
Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2.
- [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases)
- [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2)

---
updated-dependencies:
- dependency-name: decode-uri-component
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-09 17:17:50 +00:00
Sid Pranjale
8b7a7192a4 Initial EDOMH integration 2022-11-27 14:55:56 +05:30
Felix Linker
6688a1fbe3 Merge pull request #728 from AndricReal/patch-1
Update of russian localisation
2022-11-27 10:11:55 +01:00
AndricReal
2ea5fe5d58 Update ru.json
Corrected translation
2022-11-20 16:08:27 +05:00
Felix Linker
efd644c6f1 Merge pull request #726 from Sid127/matlist-fix
Matlist fix
2022-11-11 20:18:22 +01:00
Sid Pranjale
17c6ec1f97 Do nothing if a module has no experimentals
This fixes materials not being shown for modules that can be engineered but not having experimental effects
2022-11-09 21:19:02 +05:30
Felix Linker
9bc6e36f14 Remove/replace react 16 dependencies 2022-10-29 11:53:36 +02:00
Felix Linker
062815054c Add package-lock 2022-10-29 11:43:02 +02:00
Felix Linker
7bc40d24d8 Merge pull request #708 from Sid127/develop
Quick fix for missing group info in JSON exports
2022-10-21 17:13:11 +02:00
Felix Linker
d9da250f50 Merge pull request #709 from Sid127/uuids-fix
Add mats for experimental effects to mat list
2022-10-21 17:11:39 +02:00
Sid Pranjale
801969dc07 Add mats for experimental effects to mat list 2022-10-09 16:34:04 +05:30
Sid Pranjale
6bf820934f Quick fix for missing group info in JSON exports 2022-10-09 11:45:18 +05:30
Felix Linker
887dbc25f8 Merge remote-tracking branch 'unknowngamer/master' into develop 2022-10-02 11:53:56 +02:00
Felix Linker
50de77d613 Merge remote-tracking branch 'origin/master' into develop 2022-10-02 11:53:48 +02:00
Felix Linker
13561ee21a Merge pull request #704 from green1052/develop
Add Korean translate
2022-10-02 11:44:48 +02:00
green1052
333feaa6bf Add Korean translate
Co-authored-by: jackfrost <jackfrost5446@naver.com>
2022-10-01 15:35:56 +09:00
Rob
1e37fd15eb Fix link to data wiki 2022-08-26 07:28:17 +01:00
ItsUnknownGamer
f82b0212b5 shield recovery time depending on distributor draw 2022-03-04 17:44:49 +01:00
Felix Linker
32138f5546 Merge pull request #689 from Sid127/develop
Minor modifications for Multi-limpet Controllers
2022-01-05 10:53:54 +01:00
Sid
85ddce14a5 Minor modifications for Multi-limpet Controllers 2022-01-05 13:09:34 +05:30
Felix Linker
7e44772f2e Hotfix 2021-12-30 17:06:02 +01:00
Felix Linker
e90bfe9b68 Multi limpet controllers have max of 1 2021-12-30 14:21:21 +01:00
Felix Linker
64002e1ae0 Add translation for multi limpet controllers 2021-12-30 13:53:07 +01:00
Felix Linker
873dfaa305 Merge pull request #676 from stephendavidmarsh/feature/fix-dpe
Fix DPE calculations on offense tab
2021-09-26 17:21:49 +02:00
Felix Linker
7050356bce Merge pull request #675 from alexeygorb/feature/fix-inara-slef-import
Fix for engineered modules in SLEF imports from Inara.
2021-09-10 16:44:11 +02:00
Alexey Gorb
88c9bb0254 Fix for engineered modules in SLEF imports from Inara. Restored the deleted code. 2021-09-06 23:46:55 +02:00
Alexey Gorb
45f1dd2da9 Fix for engineered modules in SLEF imports from Inara. 2021-08-16 00:04:14 +02:00
Alexey Gorb
46bcc2313f Moved the resistance modifiers value calculation to the setModValue of a Module class. 2021-08-15 14:37:45 +02:00
Stephen Marsh
9e012c1490 Fix DPE calculations on offense tab 2021-08-11 10:15:23 -04:00
Alexey Gorb
50f9c0faa1 Fix for engineered modules in SLEF imports from Inara. Unused function removed. 2021-08-11 12:04:25 +02:00
Alexey Gorb
74829a09c0 Fix for engineered modules in SLEF imports from Inara. 2021-08-11 11:55:24 +02:00
Felix Linker
45508ba2d4 Merge pull request #673 from alexeygorb/feature/fix-inara-slef-import
Fix for engineered modules in SLEF imports from Inara.
2021-08-10 12:57:42 +02:00
Alexey Gorb
624adf2b64 Fix for engineered modules in SLEF imports from Inara. 2021-08-10 11:35:44 +02:00
Felix Linker
821daefeb8 Merge branch 'develop' 2021-08-08 11:24:07 +02:00
Felix Linker
86b95981f1 Merge pull request #666 from Athanasius/enhancement/readme-mention-npm-run-build
README: Call out `npm run build` alternative in developer instructions
2021-08-04 10:37:42 +02:00
Felix Linker
688eebb9ea Merge pull request #668 from gopstr/improve_russian
Update Russian translation
2021-08-04 10:36:09 +02:00
Pavel Strybuk
d719da2cde Translate 3 forgotten items 2021-06-12 22:22:37 +03:00
Pavel Strybuk
19c1851e14 Improve Russian translation 2021-06-12 21:59:29 +03:00
Pavel Strybuk
414bf4cb20 Translate selected experimental effect in dropdown 2021-06-12 21:59:25 +03:00
Pavel Strybuk
c674459376 Fix misprint 2021-06-12 21:59:21 +03:00
Athanasius
fd009fe567 README: Make a 'Deployment' section and move instructions there. 2021-05-28 11:41:04 +01:00
Athanasius
e28eccb6fb README: Call out npm run build alternative in developer instructions 2021-05-28 10:29:59 +01:00
Felix Linker
301c97db58 Merge pull request #663 from leonardofelin/master
Update pt.json
2021-05-22 15:48:51 +02:00
Felix Linker
c19ca6648d Update README 2021-05-14 10:37:16 +02:00
leonardofelin
cfdb92ecc6 Update pt.json 2021-05-12 16:49:56 -03:00
Felix Linker
de5ca7b5e6 Revert "Corrected calculations of modification values"
This reverts commit 49c827b2c8.
2021-05-12 08:49:00 +02:00
Felix Linker
8676deba7d Merge branch 'develop' 2021-05-10 20:18:34 +02:00
Felix Linker
d3ce8d4f7c Remove unused hosting and CI files 2021-05-10 20:18:07 +02:00
Felix Linker
0c3de95025 Merge pull request #658 from leonardofelin/patch-2
Update pt.js
2021-05-09 18:36:10 +02:00
Felix Linker
83f1f9aa2e Merge pull request #657 from leonardofelin/patch-1
Update pt.json
2021-05-09 13:01:52 +02:00
leonardofelin
dee14a5dee Update pt.json
Fixed.
2021-05-09 07:31:19 -03:00
Felix Linker
db13da95db Merge pull request #659 from VAKazakov/patch-2
update recon and research limpets translation for de
2021-05-09 10:20:42 +02:00
VAKazakov
cb08b10a63 update recon and research limpets translation
see conversation on [EDCD Discord](https://discord.com/channels/164411426939600896/164411501166198784/836315370632511511 "EDCD Discord")
2021-04-26 22:27:41 +03:00
leonardofelin
189eb2b726 Update pt.js
Full portuguese review.
2021-04-20 14:44:57 -03:00
leonardofelin
b9abf784f4 Update pt.json
Full review of Portuguese Language.
2021-04-20 14:40:49 -03:00
Felix Linker
39287bc5d7 Remove dead links from README 2021-03-27 15:22:15 +01:00
Felix Linker
bcdd0c6044 Display negative module discounts correctly
Closes #563
2021-01-31 20:26:38 +01:00
Felix Linker
f70455ce26 Remove manufacturer from overview
Closes #366
2021-01-31 20:20:17 +01:00
Felix Linker
888f807a7b Remove orbis related code 2021-01-31 16:57:55 +01:00
Felix Linker
5040141096 Remove dead announcements code 2021-01-31 16:52:40 +01:00
Felix Linker
46ba782911 Remove hosting from repository 2021-01-31 16:52:21 +01:00
Felix Linker
524e204e01 Merge pull request #627 from jeneg/calculations-fix
Corrected calculations of modification values
2021-01-27 20:16:29 +01:00
Felix Linker
a9753828c1 Merge pull request #639 from VAKazakov/VAKazakov-ru-l18n-1
Some Ru l18n additions
2021-01-25 20:14:29 +01:00
VAKazakov
6d30a54925 fixing encoding and deleting excess translation 2021-01-25 22:10:17 +03:00
Felix Linker
7c58eb1cde Merge pull request #637 from kf6nux/patch-1
Remove dead code
2021-01-25 20:09:16 +01:00
VAKazakov
4001e1e9ac changing : to : 2021-01-25 21:54:43 +03:00
VAKazakov
0da00d38a4 fixed indentation for easy comparing 2021-01-25 21:47:44 +03:00
VAKazakov
1db6fe1a34 Some l18n additions
guardians and mining rows addition
courtesy of LightEvil
2021-01-25 21:41:32 +03:00
VAKazakov
10b8bb95a9 Merge pull request #1 from EDCD/master
Actualization of branch
2021-01-25 21:39:13 +03:00
Ben Zarzycki
8d9581900f Remove dead code 2021-01-20 00:05:45 -08:00
Felix Linker
2bb55d2c36 Remove donation references 2021-01-06 12:11:44 +01:00
yevhenii.chubar
49c827b2c8 Corrected calculations of modification values 2020-12-29 17:47:10 +02:00
Felix Linker
cf50537e3d Fix diminishing returns for alloys
Closes #483
2020-11-16 23:27:24 +01:00
Felix Linker
804466f88a Allow SLEF import of multiple builds via URL 2020-10-24 13:20:02 +02:00
Felix Linker
bded793374 Merge pull request #559 from Freaky/develop
Shield engineering should not modify max mass
2020-10-24 12:54:42 +02:00
Felix Linker
fc918d893c Support SLEF on import 2020-10-24 12:37:34 +02:00
Felix Linker
5fe13b26a4 Merge pull request #561 from Freaky/reserve-tank-jump-mass
Calculate jump range with a full reserve fuel tank
2020-10-24 11:46:32 +02:00
Felix Linker
be1393994e Merge pull request #554 from VAKazakov/patch-1
RU Localisation fixes
2020-10-24 11:22:54 +02:00
Felix Linker
dc6db31d43 Merge pull request #596 from jontaylor/feature/add_dpe_to_offence_stats
Feature/add dpe to offence stats
2020-10-24 11:22:11 +02:00
Jon Taylor
840ce9f3e4 Remove logging 2020-07-21 17:24:06 +01:00
Jon Taylor
9674aa2367 Add the effective DPE for Shields and Armor to offence table 2020-07-21 17:20:23 +01:00
William
d19b6b107f Merge pull request #594 from GamerKingFaiz/master
Minor typo fix
2020-06-28 09:02:30 +10:00
Faiz Rahman
1b96c18ecb Minor typo fix. 2020-06-26 16:29:23 -07:00
Felix Linker
0f43c4d7eb Merge pull request #592 from pho3nixf1re/feature/support-slef-import
Support SLEF import format for importing builds.
2020-06-24 09:41:11 +02:00
Matthew Turney
4c70806a5a Support SLEF import format for importing builds.
Inara uses the [SLEF] format to export builds. This format is mostly
just a wrapper around the standard journal loadout format and includes
support for source app metadata and exporting of multiple loadouts at
one time. This change adds support for this format in the manual
importer. Eventually it would be good to support this in the import
route as well so Inara (or any other apps) can link directly to
coriolis.

[SLEF]: https://inara.cz/inara-impexp-slef/
2020-06-14 12:38:24 -05:00
Matthew Turney
7de304bdbe Tests for ImportModal are failing
All of the compressed data in the Persisted storage has subtly changed.
It is not entirely clear why it has changed and the imports still
function correctly.

Another change is the 'Import Backup' tests. At some point there was a
change to just remove invalid builds instead of throwing validation
errors. The tests were never updated to fit this use-case.
2020-06-14 12:38:09 -05:00
Matthew Turney
34f9f28c16 Import tests fail due to Jest API change with mock functions 2020-06-14 10:51:39 -05:00
Thomas Hurst
13ec027772 Calculate jump range with a full reserve fuel tank (#560) 2020-04-10 18:52:39 +00:00
willyb321
3966f92454 fix webpack config 2020-04-05 05:22:35 +10:00
William
38f72438dd Update index.ejs 2020-03-25 15:13:18 +11:00
willyb321
0179382379 Merge branch 'develop' 2020-03-12 06:34:26 +11:00
willyb321
7f5c652f49 test 2020-03-12 06:33:27 +11:00
willyb321
1f9b1e5d27 invalidate docker cache for git pull 2020-03-12 06:01:11 +11:00
willyb321
ebf4491901 test 2020-03-12 05:57:47 +11:00
willyb321
d322a47592 fix 2020-03-12 05:30:37 +11:00
William Blythe
06a58d22cb Merge branch 'master' into develop 2020-03-12 05:24:04 +11:00
William
25d4520eee Update index.ejs 2020-03-01 19:13:13 +11:00
William
0087062468 Update index.ejs 2020-03-01 10:37:16 +11:00
William
14bb49a2bc Update index.ejs 2020-03-01 10:27:39 +11:00
William
ab671b0af5 Update docker-compose.yml 2020-01-23 07:33:53 +11:00
William
304ddf9ea8 Update docker-compose.yml 2020-01-23 07:33:23 +11:00
William
b3f320e69f Update docker-compose.yml 2020-01-23 07:32:16 +11:00
William
3a63e08f80 Update docker-compose.yml 2020-01-23 07:29:42 +11:00
William
a3feb42fd7 Create Dockerfile.dev 2020-01-23 07:29:20 +11:00
William
a77d991cf9 Update Dockerfile 2020-01-23 07:28:59 +11:00
William
9ebe5dc786 update paypal donation 2020-01-15 08:08:38 +11:00
Thomas Hurst
baace95f83 Shield engineering should not modify max mass (#473) 2019-11-30 01:19:00 +00:00
VAKazakov
fc5db94f9a Localisation fixes
Changed ru localisation for params "ammo" and "clip" to in-game variants
https://media.discordapp.net/attachments/301454399597969409/640856772952850462/001.PNG
2019-11-04 13:28:30 +03:00
willyb321
c3b0e8d949 fix announcements 2019-10-13 07:39:05 +11:00
willyb321
1b8c460876 orbis fixes 2019-10-13 07:16:19 +11:00
willyb321
67409a613b add migrate page, need to make it redirect still 2019-09-27 07:45:03 +10:00
willyb321
e4a826592f remove ads, trial didnt work 2019-09-27 07:15:13 +10:00
willyb321
cee4c32551 Merge branch 'master' into develop 2019-09-23 08:47:08 +10:00
willyb321
081d8fb86a google analytics 2019-09-23 08:41:34 +10:00
willyb321
3dfd563d90 testing ads again 2019-09-23 08:39:53 +10:00
William
fd08cd219c Merge pull request #546 from EDCD/fix/bugs
Various bug fixes
2019-09-18 05:59:58 +10:00
sascha-dev
6a15326d31 Wrong translation corrected. 2019-09-15 12:46:41 +02:00
sascha-dev
608ecc51b7 german translation extended 2019-09-15 12:46:41 +02:00
sascha-dev
fcef26ebbb german translation extended. fixed some typos. 2019-09-15 12:46:41 +02:00
Felix Linker
ba6d758ed5 Import plasma slug correctly
References #450
2019-09-13 22:36:56 +02:00
Felix Linker
43aa3e4e79 Merge pull request #541 from monopoint/export_select
bugfix: unable to copy from export textarea (#540)
2019-09-11 22:27:23 +02:00
Unknown
18f0e060a7 fix for scrollbar on demand 2019-09-11 22:16:55 +02:00
EspenMH
c7547e8baf shipyard: group compared, scrollbar on demand, borders 2019-09-11 22:16:55 +02:00
spinmh
ffff242abe userselect: auto does not resove correct if parent has user-select: none (it has)
set explicitly to "text"
2019-09-07 18:42:48 +02:00
Felix Linker
b44c66b986 Merge pull request #536 from monopoint/develop
448: compare highlights in shipyard
2019-08-25 13:32:08 +02:00
Felix Linker
ae77ec6256 Implement blueprint changes to focused weapon 2019-08-24 15:36:51 +02:00
Felix Linker
4f1e32b154 Move sustained factor calculation into own function
Closes #517
2019-08-24 14:16:50 +02:00
Felix Linker
af37c2bfc5 Fix import falloff
References #535
2019-08-24 14:16:50 +02:00
felixlinker
5d4ab6f2ad Disabling guardian shield/module/hull reinforcement packages has an effect now
Closes #523
2019-08-24 14:16:49 +02:00
felixlinker
0c9db53057 Use shield and armour metrics in ship summary table 2019-08-24 14:16:49 +02:00
felixlinker
b689605ac2 Maximum active limpets does not show twice now
Closes #532
2019-08-19 18:59:11 +02:00
felixlinker
baab91e371 Rename "Boost Time" to "Boost Interval"
Closes #537
2019-08-19 18:52:15 +02:00
EspenMH
70e69c7099 448: removed double quotes in js. slight text highlight on compared ships in shipyard 2019-08-15 15:06:37 +02:00
spinmh
f4534fd3eb Remove manual ShouldComponentUpdate eval 2019-08-15 02:19:38 +02:00
spinmh
93594e1a65 Compare Highlight in Shipyard 2019-08-15 02:18:00 +02:00
William
b5e449ea54 Merge pull request #525 from TranslucentSabre/feature/fix_module_discount_import
Fix backup import to properly handle module discount.
2019-07-01 08:18:07 +10:00
William
0ff4b849aa Update Header.jsx 2019-06-29 11:34:58 +10:00
William
b99e38043f Update Header.jsx 2019-06-29 11:34:03 +10:00
Timothy Myers
28e3a59473 Fix backup import to properly handle module discount. 2019-06-24 11:22:10 -05:00
Felix Linker
b20290fb60 Add mention of compiled and hosted coriolis-data-files in README 2019-05-27 21:18:54 +02:00
willyb321
2734beb6f8 Merge remote-tracking branch 'origin/master' 2019-05-23 16:59:02 +10:00
willyb321
345eec528c RIP 2019-05-23 16:58:14 +10:00
Felix Linker
7a17e18a76 Remove undefined variable 2019-05-19 23:22:07 +02:00
willyb321
4697677457 Merge branch 'develop' 2019-05-20 06:36:43 +10:00
felixlinker
7d8a5a1368 Clear original slot if an experimental is copied and exceeds the allowed number
References #367
2019-05-19 16:01:48 +02:00
felixlinker
dd7402bd0e Allow experimental modules to be replaced with such even if 4 are present
References #367
2019-05-19 16:00:16 +02:00
felixlinker
65592b0fc6 Allow any thruster to be applied
Closes #512
2019-05-19 15:59:33 +02:00
felixlinker
0ab66023a6 Update calculation of SHPS/SEPS to match DPS~SDPS
References #504
2019-05-19 01:25:51 +02:00
felixlinker
d6fad098ee Restrict experimental weapons to at 4 at max
Closes #367
2019-05-19 01:12:50 +02:00
felixlinker
1b5730d337 Revert "Max 4 items per row when selecting modules"
This reverts commit 9b6b1d328c.
2019-05-19 00:49:09 +02:00
felixlinker
439b615b1b Revert "Don't allow more than four experimental weapons at once"
This reverts commit ac2e2e4d69.
2019-05-19 00:41:12 +02:00
Felix Linker
a8b30594dc Merge pull request #511 from EDCD/fix/data
Not more than 4 experimentals
2019-05-18 12:48:44 +02:00
felixlinker
9b6b1d328c Max 4 items per row when selecting modules 2019-05-18 12:16:39 +02:00
felixlinker
ac2e2e4d69 Don't allow more than four experimental weapons at once
Closes https://github.com/EDCD/coriolis/issues/367
2019-05-18 12:01:31 +02:00
Felix Linker
3a5fb31860 Merge pull request #508 from EDCD/fix/dps
Fixes to DPS and ROF calculations
2019-05-17 22:57:32 +02:00
felixlinker
c610eb8627 Fix burst(rof) modding and burst sdps calculation
References #504 #388
2019-05-17 21:02:17 +02:00
felixlinker
94980270c4 Fix SDPS calculations
Closes #504
2019-05-17 20:22:57 +02:00
felixlinker
c685e002e3 Fastest range is farthest range
Closes #471
2019-05-17 19:43:12 +02:00
felixlinker
1f665eed9e We don't need any time to remove no shields
Closes #426
2019-05-17 18:51:09 +02:00
felixlinker
0c4fc1fd9a Correctly update jump range for turned off modules
Closes #506
2019-05-17 18:40:20 +02:00
felixlinker
0fc033363e Don't handle inverting rof twice
Closes #500
2019-05-17 17:42:51 +02:00
willyb321
fb6e9538bc change service worker caching 2019-04-29 06:44:03 +10:00
willyb321
95b98fc4ed remove google analytics 2019-04-29 06:33:11 +10:00
Felix Linker
1840dafed0 Fix rate of fire modification (#497)
* Fix absolute setting of burst/burstrof

* Improve change display for stats with overwrite

* Module.getPretty takes synthetic getters into account

* Allow custom higherbetter for changes of stats when formatted

* Fix rof modding
2019-04-29 06:05:43 +10:00
willyb321
1ad82b116c Merge branch 'develop' 2019-04-27 10:31:37 +10:00
Felix Linker
7bdd17504b Fixes (#495)
* Create class-child module for selection when comparing

Closes #198

* Remove broken exception for shieldboost in getPercent

Closes #274

* Remove barely used functions
2019-04-27 10:29:49 +10:00
willyb321
2d820bb5d5 test 2019-04-27 10:22:21 +10:00
willyb321
2e14512ed8 Merge branch 'develop' 2019-04-24 07:33:52 +10:00
willyb321
48ed583c6d Merge branch 'feature/3.4' into develop 2019-04-24 07:32:35 +10:00
willyb321
dd444a17f3 handle journal-style imports
Closes #391
2019-04-17 11:06:48 +10:00
willyb321
2ea63c711e docking computer is now a unique module
you cannot have more than 1 standard dc, or a standard and an advanced dc, nor more than 1 advanced
2019-04-15 07:01:08 +10:00
willyb321
6d6d31db25 rename assists to flight assists 2019-04-15 07:00:48 +10:00
willyb321
e9273dcb9b move dc to assists category, 3.4 prep 2019-04-15 06:43:55 +10:00
willyb321
2bdc4562c6 add rg to autofill 2019-04-05 08:51:37 +11:00
willyb321
9e8a5323e9 Merge branch 'develop' 2019-03-31 10:34:37 +11:00
willyb321
8e001063b3 fix regex 2019-03-31 10:34:22 +11:00
willyb321
dc88fab4c5 Merge branch 'develop' 2019-03-31 10:24:03 +11:00
willyb321
dfca917e50 build image in docker compose 2019-03-31 10:23:41 +11:00
willyb321
ef7dfd6ca1 checkout branch in docker build 2019-03-31 10:17:58 +11:00
89 changed files with 18828 additions and 3911 deletions

View File

@@ -7,6 +7,8 @@
"@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",
@@ -28,7 +30,7 @@
}
],
"@babel/plugin-proposal-nullish-coalescing-operator",
"@babel/plugin-proposal-do-expressions",
"@babel/plugin-proposal-function-bind"
["@babel/plugin-proposal-private-methods", { "loose": true }],
["@babel/plugin-proposal-private-property-in-object", { "loose": true }]
]
}

View File

@@ -1,77 +1,7 @@
Dockerfile
.dockerignore
.gitignore
README.md
build
node_modules
npm-debug.log
### Node template
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless

14
.gitattributes vendored Normal file
View File

@@ -0,0 +1,14 @@
# 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

28
.github/workflows/autodeploy.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
# 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/

1
.gitignore vendored
View File

@@ -10,4 +10,3 @@ env
.project
.vscode/
docs/
package-lock.json

View File

@@ -1,13 +0,0 @@
image: docker:stable
services:
- docker:dind
stages:
- Build image
docker build:
stage: Build image
script:
- img build --build-arg branch=$CI_COMMIT_REF_NAME -t edcd/coriolis:$CI_COMMIT_REF_NAME .
- echo "$REGISTRY_PASSWORD" | img login --username "$REGISTRY_USER" --password-stdin
- img push edcd/coriolis:$CI_COMMIT_REF_NAME

1
.npmrc
View File

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

View File

@@ -1,16 +0,0 @@
language: node_js
notifications:
email: false
sudo: false
node_js:
- "4.8.1"
cache:
directories:
- node_modules
before_install:
- git clone https://github.com/EDCD/coriolis-data.git ../coriolis-data
script:
- npm run lint
- npm test

View File

@@ -1,34 +1,25 @@
### STAGE 1: Build ###
FROM node:9.11.1-alpine as builder
ARG branch=develop
ENV BRANCH=$branch
WORKDIR /src/app
RUN mkdir -p /src/app/coriolis
RUN mkdir -p /src/app/coriolis-data
#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 .
RUN apk add --update git
FROM node:18-alpine
COPY . /src/app/coriolis
# 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/
RUN npm i -g npm
# Git is required before install if any modules (like coriolis-data) are loaded from github
RUN apk update
RUN apk add git
# Set up coriolis-data
WORKDIR /src/app/coriolis-data
RUN git clone https://github.com/EDCD/coriolis-data.git .
RUN git checkout ${BRANCH}
RUN npm install --no-package-lock
RUN npm start
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)
# Set up coriolis
WORKDIR /src/app/coriolis
RUN npm install --no-package-lock
RUN npm run build
### STAGE 2: Production Environment ###
FROM fholzer/nginx-brotli as web
COPY nginx.conf /etc/nginx/nginx.conf
COPY --from=builder /src/app/coriolis/build /usr/share/nginx/html
WORKDIR /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-c", "/etc/nginx/nginx.conf", "-g", "daemon off;"]
# Optimally, this will start a static asset server like nginx/apache. Currently, this will start dev webpack server.
CMD ["npm", "start"]
EXPOSE 3300

23
Dockerfile-dev Normal file
View File

@@ -0,0 +1,23 @@
#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

32
Dockerfile-local-prod Normal file
View File

@@ -0,0 +1,32 @@
#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,4 +1,4 @@
![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)
[![Chat to us on Discord](https://img.shields.io/badge/Discord-EDCD%20%23coriolis-blue.svg?style=social)](https://discord.gg/0uwCh6R62aPRjk9w)
## About
@@ -8,51 +8,50 @@ Coriolis was created using assets and imagery from Elite: Dangerous, with the pe
## Contributing
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.
### 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)!
- [Submit issues](https://github.com/EDCD/coriolis/issues)
- [Submit pull requests](https://github.com/EDCD/coriolis/pulls) targetting `develop` branch
- Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
## Development
See the [Developer's Guide](https://github.com/EDCD/coriolis/wiki/Developing-for-Coriolis) in the wiki.
This release includes the ability to run the app as a Docker container.
```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
```
Also see [the documentation site.](https://coriolis.willb.info/)
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
See the [Data wiki](https://github.com/cmmcleod/coriolis-data/wiki) for details on structure, etc.
See the [Data wiki](https://github.com/EDCD/coriolis-data/wiki) for details on structure, etc.
## Deployment
## License
Follow the steps for [Development](#development) as above, but instead
of `npm start` you'll want to:
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/).
```sh
> npm run build
```
The code (Javascript, CSS, HTML, and SVG files only) specificially for Coriolis.io is released under the MIT License.
this will result in a `build/` directory being created containing all the necessary files.
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.
After this you need to serve the files in some manner.
Either configure your webserver to make the actual `build/` directory
visible on the web, or alternatively copy it to somewhere to serve it
from.

View File

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

View File

@@ -0,0 +1,366 @@
[
{
"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

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

View File

@@ -0,0 +1,188 @@
[
{
"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 = {
language: getLanguage('en'),
sizeRatio: 1,
openMenu: jest.genMockFunction(),
closeMenu: jest.genMockFunction(),
showModal: jest.genMockFunction(),
hideModal: jest.genMockFunction(),
tooltip: jest.genMockFunction(),
termtip: jest.genMockFunction(),
onWindowResize: jest.genMockFunction()
openMenu: jest.fn(),
closeMenu: jest.fn(),
showModal: jest.fn(),
hideModal: jest.fn(),
tooltip: jest.fn(),
termtip: jest.fn(),
onWindowResize: jest.fn()
};
let modal, render, ContextProvider = Utils.createContextProvider(mockContext);
@@ -110,21 +110,25 @@ describe('Import Modal', function() {
it('catches an invalid backup', function() {
const importData = require('./fixtures/valid-backup');
let invalidImportData = Object.assign({}, importData);
//invalidImportData.builds.asp = null; // Remove Asp Miner build used in comparison
// Remove Asp Miner build used in comparison
delete(invalidImportData.builds.asp);
pasteText('"this is not valid"');
expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual('Must be an object or array!');
pasteText('{ "builds": "Should not be a string" }');
expect(modal.state.importValid).toBeFalsy();
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.errorMsg).toEqual('"invalid_ship" is not a valid Ship Id!');
expect(Object.keys(modal.state.builds)).not.toContain('anaconda');
pasteText(JSON.stringify(importData).replace('Dream', ''));
expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual('Imperial Clipper build "" must be a string at least 1 character long!');
expect(Object.keys(modal.state.builds.imperial_clipper).length).toEqual(3);
pasteText(JSON.stringify(invalidImportData));
expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual('asp build "Miner" data is missing!');
@@ -144,7 +148,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.&bn=Test%20My%20Ship');
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b-.AwRj4zNLeI%3D%3D.CwBhCYzBGW9qCTSqq5JA.&bn=Test%20My%20Ship');
});
it('catches an invalid build', function() {
@@ -169,7 +173,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
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');
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');
});
});
@@ -186,7 +190,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
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');
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');
});
it('imports a valid v4 build with modifications', function() {
@@ -198,11 +202,11 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
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');
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');
});
});
describe('Import Detaild Builds Array', function() {
describe('Import Detailed Builds Array', function() {
beforeEach(reset);
@@ -240,7 +244,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
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');
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr--v66g--.AwRj4zNapI%3D%3D.CwRgDBldUExuBiIlWIA%3D.&bn=Imported%20Federal%20Corvette');
});
it('imports a valid companion API build', function() {
@@ -252,7 +256,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
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');
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/beluga?code=A0pktsFplCdpsnf70t0t2727270004040404043c4fmimlmm04mc0iv62i--.AwRj4yusg%3D%3D%3D.CwRgDBldHi8IWIA%3D.&bn=Imported%20Beluga%20Liner');
});
it('imports a valid companion API build', function() {
@@ -264,7 +268,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
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');
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/type_7_transport?code=A0patfFflidasdf5----0404040005050504044d2402--.AwRj4yoo.CwRgDBlVK7HjEA%3D%3D.&bn=Imported%20Type-7%20Transporter');
});
it('imports a valid companion API build', function() {
@@ -276,7 +280,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/cobra_mk_iii?code=A0p0tdFaldd3sdf4------34---2f2i.AwRj4yKA.CwRgDMYExrezBUg%3D.&bn=Imported%20Cobra%20Mk%20III');
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/cobra_mk_iii?code=A0p0tdFaldd3sdf4------34----2i--.AwRj4yqA.CwRgDMYExrezBig%3D.&bn=Imported%20Cobra%20Mk%20III');
});
});
@@ -324,4 +328,41 @@ 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]);
}
}
});
});
});

View File

@@ -3,24 +3,13 @@ var WebpackDevServer = require("webpack-dev-server");
var config = require('./webpack.config.dev');
new WebpackDevServer(webpack(config), {
publicPath: config.output.publicPath,
hot: true,
disableHostCheck: true,
headers: { "Access-Control-Allow-Origin": "*" },
historyApiFallback: {
rewrites: [
// For some reason connect-history-api-fallback does not allow '.' in the URL for history fallback...
{ 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) {
if (err) {

View File

@@ -1,52 +0,0 @@
version: '2.2'
services:
coriolis_prod:
image: edcd/coriolis:master
restart: always
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
networks:
- web
labels:
- "traefik.docker.network=web"
- "traefik.frontend.redirect.regex=^https:\/\/coriolis\.edcd\.io\/(.*)"
- "traefik.frontend.redirect.replacement=https://coriolis.io/$1"
- "traefik.enable=true"
- "traefik.basic.frontend.rule=Host:coriolis.io,coriolis.edcd.io"
- "traefik.basic.port=80"
- "traefik.basic.protocol=http"
coriolis_dev:
image: edcd/coriolis:develop
restart: always
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
networks:
- web
labels:
- "traefik.docker.network=web"
- "traefik.enable=true"
- "traefik.frontend.redirect.regex=^https:\/\/beta\.coriolis\.edcd\.io\/(.*)"
- "traefik.frontend.redirect.replacement=https://beta.coriolis.io/$1"
- "traefik.basic.frontend.rule=Host:beta.coriolis.io,beta.coriolis.edcd.io"
- "traefik.basic.port=80"
- "traefik.basic.protocol=http"
coriolis_dw2:
image: edcd/coriolis:dw2
restart: always
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
networks:
- web
labels:
- "traefik.docker.network=web"
- "traefik.enable=true"
- "traefik.basic.frontend.rule=Host:dw2.coriolis.io"
- "traefik.basic.port=80"
- "traefik.basic.protocol=http"
networks:
web:
external: true

View File

@@ -1,96 +0,0 @@
worker_processes 1;
user nobody nobody;
error_log /tmp/error.log;
pid /tmp/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
client_body_temp_path /tmp/client_body;
fastcgi_temp_path /tmp/fastcgi_temp;
proxy_temp_path /tmp/proxy_temp;
scgi_temp_path /tmp/scgi_temp;
uwsgi_temp_path /tmp/uwsgi_temp;
access_log /tmp/access.log;
error_log /tmp/error.log;
# https://nginx.org/en/docs/http/ngx_http_gzip_module.html
# Enable gzip compression.
# Default: off
gzip off;
# Compression level (1-9).
# 5 is a perfect compromise between size and CPU usage, offering about
# 75% reduction for most ASCII files (almost identical to level 9).
# Default: 1
gzip_comp_level 5;
# Don't compress anything that's already small and unlikely to shrink much
# if at all (the default is 20 bytes, which is bad as that usually leads to
# larger files after gzipping).
# Default: 20
gzip_min_length 256;
# Compress data even for clients that are connecting to us via proxies,
# identified by the "Via" header (required for CloudFront).
# Default: off
gzip_proxied any;
# Tell proxies to cache both the gzipped and regular version of a resource
# whenever the client's Accept-Encoding capabilities header varies;
# Avoids the issue where a non-gzip capable client (which is extremely rare
# today) would display gibberish if their proxy gave them the gzipped version.
# Default: off
gzip_vary on;
# Compress all output labeled with one of the following MIME-types.
# text/html is always compressed by gzip module.
# Default: text/html
gzip_types *;
brotli on;
# brotli_static on;
brotli_types *;
# This should be turned on if you are going to have pre-compressed copies (.gz) of
# static files available. If not it should be left off as it will cause extra I/O
# for the check. It is best if you enable this in a location{} block for
# a specific directory, or on an individual server{} level.
# gzip_static on;
keepalive_timeout 3000;
server {
listen 80;
listen [::]:80;
index index.html;
server_name localhost;
root /usr/share/nginx/html;
autoindex on;
location ~* \.(?:manifest|appcache|html?|xml|json|css|js|map|jpg|jpeg|gif|png|ico|svg|eot|ttf|woff|woff2)$ {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
access_log off;
}
location /service-worker.js {
expires -1;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-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;
}
location /iframe.html {
try_files $uri $uri/ /iframe.html =404;
}
}
}

12992
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,19 @@
{
"name": "coriolis_shipyard",
"version": "3.0.0",
"version": "3.0.1",
"repository": {
"type": "git",
"url": "https://github.com/EDCD/coriolis"
},
"homepage": "https://coriolis.io",
"bugs": "https://github.com/EDCD/coriolis/issues",
"contributors": [
{ "name": "cmdrmcdonald" },
{ "name": "willb321" },
{ "name": "felixlinker" }
],
"private": true,
"engine": "node >= 4.8.1",
"engine": "node >= 10.13.0",
"license": "MIT",
"scripts": {
"extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f",
@@ -18,7 +23,8 @@
"test": "jest",
"prod-serve": "nginx -p $(pwd) -c nginx.conf",
"prod-stop": "kill -QUIT $(cat nginx.pid)",
"build": "npm run clean && cross-env NODE_ENV=production webpack -p --config webpack.config.prod.js",
"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 && 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",
"deploy": "npm run lint && npm test && npm run build && npm run rsync"
},
@@ -55,7 +61,7 @@
]
},
"devDependencies": {
"@babel/core": "^7.0.0",
"@babel/core": "^7.20.12",
"@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/plugin-proposal-decorators": "^7.0.0",
"@babel/plugin-proposal-do-expressions": "^7.0.0",
@@ -74,15 +80,12 @@
"@babel/plugin-syntax-import-meta": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"appcache-webpack-plugin": "^1.4.0",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^10.0.1",
"babel-jest": "^23.6.0",
"@rollup/plugin-node-resolve": "^15.0.1",
"babel-loader": "^8.0.0",
"copy-webpack-plugin": "^4.5.2",
"copy-webpack-plugin": "^10.2.4",
"create-react-class": "^15.6.3",
"cross-env": "^5.2.0",
"css-loader": "^1.0.0",
"css-loader": "^6.7.3",
"d3-selection": "^1.3.2",
"esdoc": "^1.1.0",
"esdoc-custom-theme": "^1.4.2",
@@ -93,54 +96,66 @@
"esdoc-standard-plugin": "^1.0.0",
"eslint": "^5.6.0",
"eslint-plugin-react": "^7.11.1",
"expose-loader": "^0.7.5",
"express": "^4.16.3",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"file-loader": "^2.0.0",
"html-webpack-plugin": "^3.0.7",
"jest-cli": "^23.6.0",
"jsen": "^0.6.4",
"expose-loader": "^3.1.0",
"express": "^4.18.2",
"html-webpack-plugin": "^5.5.0",
"jsen": "^0.6.6",
"json-loader": "^0.5.4",
"less": "^3.8.1",
"less-loader": "^4.1.0",
"react-addons-perf": "^15.4.2",
"less-loader": "^11.1.0",
"mini-css-extract-plugin": "^2.7.2",
"react-container-dimensions": "^1.4.1",
"react-testutils-additions": "^16.0.0",
"react-testutils-additions": "^15.0.0",
"react-transition-group": "^2.5.0",
"rimraf": "^2.6.1",
"rollup": "^0.66.2",
"rollup-plugin-node-resolve": "^3.4.0",
"style-loader": "^0.23.0",
"uglify-js": "^3.4.9",
"url-loader": "^1.1.1",
"webpack": "^4.20.2",
"webpack-bugsnag-plugins": "^1.2.2",
"webpack-cli": "^3.1.1",
"webpack-dev-server": "^3.1.9",
"webpack-notifier": "^1.6.0",
"workbox-webpack-plugin": "^3.6.1"
"rimraf": "^4.1.2",
"rollup": "^3.17.2",
"style-loader": "^3.3.1",
"uglify-js": "^3.17.4",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1",
"webpack-merge": "^5.8.0",
"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": {
"@babel/polyfill": "^7.0.0",
"assert": "^1.5.0",
"auto-bind": "^5.0.1",
"base64url": "^3.0.1",
"browserify-zlib-next": "^1.0.1",
"buffer": "^5.7.0",
"classnames": "^2.2.6",
"constants-browserify": "^1.0.0",
"core-js": "^3.28.0",
"coriolis-data": "../coriolis-data",
"crypto-browserify": "^3.12.0",
"d3": "^5.7.0",
"detect-browser": "^3.0.1",
"fbemitter": "^2.1.1",
"https-browserify": "^1.0.0",
"lodash": "^4.17.11",
"lz-string": "^1.4.4",
"pako": "^1.0.6",
"os-browserify": "^0.3.0",
"pako": "^2.1.0",
"path-browserify": "^1.0.1",
"prop-types": "^15.6.2",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-extras": "^0.7.1",
"react": "^15.6.2",
"react-dom": "^15.6.2",
"react-fuzzy": "^0.5.2",
"react-ga": "^2.5.3",
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
"react-number-editor": "^4.0.3",
"recharts": "^1.2.0",
"register-service-worker": "^1.5.2",
"superagent": "^3.8.3"
"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 {
entry: "d3-funcs.js",

View File

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

View File

@@ -22,6 +22,7 @@ import ComparisonPage from './pages/ComparisonPage';
import ShipyardPage from './pages/ShipyardPage';
import ErrorDetails from './pages/ErrorDetails';
const zlib = require('pako');
const request = require('superagent');
@@ -72,7 +73,6 @@ export default class Coriolis extends React.Component {
route: {},
sizeRatio: Persist.getSizeRatio()
};
this._getAnnouncements();
Router('', (r) => this._setPage(ShipyardPage, r));
Router('/import?', (r) => this._importBuild(r));
Router('/import/:data', (r) => this._importBuild(r));
@@ -93,34 +93,39 @@ export default class Coriolis extends React.Component {
_importBuild(r) {
try {
// Need to decode and gunzip the data, then build the ship
const data = zlib.inflate(new Buffer(r.params.data, 'base64'), { to: 'string' });
const data = zlib.inflate(new Buffer.from(r.params.data, 'base64'), { to: 'string' });
const json = JSON.parse(data);
console.info('Ship import data: ');
console.info(json);
let ship;
if (json && json.modules) {
ship = CompanionApiUtils.shipFromJson(json);
} else if (json && json.Modules) {
ship = JournalUtils.shipFromLoadoutJSON(json);
let ship, importString;
if (json) {
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}/>);
}
r.params.ship = ship.id;
r.params.code = ship.toString();
this._setPage(OutfittingPage, r)
} catch (err) {
this._onError('Failed to import ship', r.path, 0, 0, err);
}
}
async _getAnnouncements() {
try {
const announces = await request.get('https://orbis.zone/api/announcement')
.query({ showInCoriolis: true });
this.setState({ announcements: announces.body });
} catch (err) {
console.error(err)
}
}
/**
* Updates / Sets the page and route context
* @param {[type]} page The page to be shown
@@ -144,13 +149,6 @@ export default class Coriolis extends React.Component {
*/
_onError(msg, scriptUrl, line, col, errObj) {
console && console.error && console.error(arguments); // eslint-disable-line no-console
if (errObj) {
if (errObj instanceof Error) {
bugsnagClient.notify(errObj); // eslint-disable-line
} else if (errObj instanceof String) {
bugsnagClient.notify(msg, errObj); // eslint-disable-line
}
}
this.setState({
error: <ErrorDetails error={{ message: msg, details: { scriptUrl, line, col, error: JSON.stringify(errObj) } }}/>,
page: null,
@@ -394,18 +392,18 @@ export default class Coriolis extends React.Component {
*/
render() {
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.message}/>)}</div>
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>
<div className="right cap">
<a href="https://github.com/EDCD/coriolis" target="_blank" rel="noopener noreferrer"
title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>

View File

@@ -1,7 +1,5 @@
import Persist from './stores/Persist';
import ReactGA from 'react-ga';
ReactGA.initialize('UA-55840909-18');
let standalone = undefined;
/**
@@ -74,6 +72,7 @@ Router.go = function(path, state) {
gaTrack(path);
let ctx = new Context(path, state);
Router.dispatch(ctx);
if (!ctx.unhandled) {
if (isStandAlone()) {
Persist.setState(ctx);
@@ -259,16 +258,8 @@ Route.prototype.match = function(path, params) {
* @param {string} path Path to track
*/
function gaTrack(path) {
const match = path.match(/\/outfit\/(.*)(\?code=.*)/);
if (match) {
if (match[1]) {
ReactGA.ga('set', 'contentGroup1', match[1]);
}
if (match[2]) {
ReactGA.ga('set', 'contentGroup2', match[2]);
}
}
ReactGA.pageview(path);
const _paq = window._paq || [];
_paq.push(['trackPageView']);
}
/**

View File

@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { autoBind } from 'react-extras';
import autoBind from 'auto-bind';
/**
* Announcement component

View File

@@ -4,7 +4,7 @@ import * as ModuleUtils from '../shipyard/ModuleUtils';
import TranslatedComponent from './TranslatedComponent';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames';
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import { CoriolisLogo, MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import FuzzySearch from 'react-fuzzy';
const PRESS_THRESHOLD = 500; // mouse/touch down threshold
@@ -20,6 +20,7 @@ const GRPCAT = {
'cc': 'limpet controllers',
'fx': 'limpet controllers',
'hb': 'limpet controllers',
'mlc': 'limpet controllers',
'pc': 'limpet controllers',
'rpl': 'limpet controllers',
'pce': 'passenger cabins',
@@ -38,13 +39,18 @@ const GRPCAT = {
'ml': 'lasers',
'c': 'projectiles',
'mc': 'projectiles',
'advmc': 'projectiles',
'axmc': 'experimental',
'axmce': 'experimental',
'ntp': 'experimental',
'fc': 'projectiles',
'rfl': 'experimental',
'pa': 'projectiles',
'rg': 'projectiles',
'mr': 'ordnance',
'amr': 'ordnance',
'axmr': 'experimental',
'axmre': 'experimental',
'rcpl': 'experimental',
'dtl': 'experimental',
'tbsc': 'experimental',
@@ -78,7 +84,13 @@ const GRPCAT = {
// Mining
'scl': 'mining',
'pwa': 'mining',
'sdm': '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
const CATEGORIES = {
@@ -88,24 +100,26 @@ const CATEGORIES = {
'fi': ['fi'],
'fuel': ['ft', 'fs'],
'hangars': ['fh', 'pv'],
'limpet controllers': ['cc', 'fx', 'hb', 'pc', 'rpl'],
'limpet controllers': ['cc', 'fx', 'hb', 'pc', 'rpl', 'mlc'],
'passenger cabins': ['pce', 'pci', 'pcm', 'pcq'],
'rf': ['rf'],
'shields': ['sg', 'bsg', 'psg', 'scb'],
'structural reinforcement': ['hr', 'mrp'],
'dc': ['dc'],
'flight assists': ['dc', 'sua'],
// Hardpoints
'lasers': ['pl', 'ul', 'bl'],
'projectiles': ['mc', 'c', 'fc', 'pa', 'rg'],
'ordnance': ['mr', 'tp', 'nl'],
'projectiles': ['mc', 'advmc', 'c', 'fc', 'pa', 'rg'],
'ordnance': ['mr', 'amr', 'tp', 'nl'],
// Utilities
'sb': ['sb'],
'hs': ['hs'],
'csl': ['csl'],
'defence': ['ch', 'po', 'ec'],
'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners
// Experimental
'experimental': ['axmc', 'axmr', 'rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr',],
'experimental': ['axmc', 'axmce', 'axmr', 'axmre', 'ntp','rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr',],
'weapon stabilizers': ['ews'],
// Guardian
'guardian': ['gpp', 'gpd', 'gpc', 'ggc', 'gsrp', 'gfsb', 'ghrp', 'gmrp', 'gsc'],
@@ -121,7 +135,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
onSelect: PropTypes.func.isRequired,
diffDetails: PropTypes.func,
m: PropTypes.object,
shipMass: PropTypes.number,
ship: PropTypes.object.isRequired,
warning: PropTypes.func,
firstSlotId: PropTypes.string,
lastSlotId: PropTypes.string,
@@ -129,10 +143,6 @@ export default class AvailableModulesMenu extends TranslatedComponent {
slotDiv: PropTypes.object
};
static defaultProps = {
shipMass: 0
};
/**
* Constructor
* @param {Object} props React Component properties
@@ -154,15 +164,15 @@ export default class AvailableModulesMenu extends TranslatedComponent {
*/
_initState(props, context) {
let translate = context.language.translate;
let { m, warning, shipMass, onSelect, modules, firstSlotId, lastSlotId } = props;
let { m, warning, onSelect, modules, ship } = props;
let list, currentGroup;
let buildGroup = this._buildGroup.bind(
this,
ship,
translate,
m,
warning,
shipMass - (m && m.mass ? m.mass : 0),
(m, event) => {
this._hideDiff(event);
onSelect(m);
@@ -208,16 +218,30 @@ export default class AvailableModulesMenu extends TranslatedComponent {
if (categories.length === 1) {
// Show category header instead of group header
if (m && grp == m.grp) {
list.push(<div ref={(elem) => this.groupElem = elem} key={category}
// If this is a missing module/weapon, skip it
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 {
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
if (category == "mh" || category == "mm"){
continue;
} else {
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
}
}
} else {
// Show category header as well as group header
if (!categoryHeader) {
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
categoryHeader = true;
if (category == "mh" || category == "mm"){
continue;
}
else {
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
categoryHeader = true;
}
}
if (m && grp == m.grp) {
list.push(<div ref={(elem) => this.groupElem = elem} key={grp}
@@ -236,7 +260,11 @@ export default class AvailableModulesMenu extends TranslatedComponent {
} else if (i.mount === 'T') {
mount = 'Turreted';
}
const fuzz = { grp, m: i, name: `${i.class}${i.rating}${mount ? ' ' + mount : ''} ${translate(grp)}` };
let special = '';
if (typeof(i.special) !== 'undefined') {
special = `(${translate(i.special)})`;
}
const fuzz = { grp, m: i, name: `${i.class}${i.rating}${mount ? ' ' + mount : ''} ${translate(grp)} ${translate(special)}` };
fuzzy.push(fuzz);
}
}
@@ -248,20 +276,36 @@ export default class AvailableModulesMenu extends TranslatedComponent {
return { list, currentGroup, fuzzy, trackingFocus };
}
/**
* 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
* @param {Ship} ship Ship the selection is for
* @param {Function} translate Translate function
* @param {Object} mountedModule Mounted Module
* @param {Function} warningFunc Warning function
* @param {number} mass Mass
* @param {function} onSelect Select/Mount callback
* @param {string} grp Group name
* @param {Array} modules Available modules
* @param {string} firstSlotId id of first slot item
* @param {string} lastSlotId id of last slot item
* @return {React.Component} Available Module Group contents
*/
_buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules, firstSlotId, lastSlotId) {
_buildGroup(ship, translate, mountedModule, warningFunc, onSelect, grp, modules) {
let prevClass = null, prevRating = null, prevName;
let elems = [];
@@ -277,15 +321,23 @@ export default class AvailableModulesMenu extends TranslatedComponent {
let itemsOnThisRow = 0;
for (let i = 0; i < sortedModules.length; 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 disabled = false;
prevName = m.name;
if (ModuleUtils.isShieldGenerator(m.grp)) {
// Shield generators care about maximum hull mass
disabled = mass > m.maxmass;
} else if (m.maxmass) {
// Thrusters care about total mass
disabled = mass + m.mass > m.maxmass;
disabled = ship.hullMass > m.maxmass;
// If the mounted module is experimental as well, we can replace it so
// the maximum does not apply
} else if (m.experimental && (!mountedModule || !mountedModule.experimental)) {
disabled = 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 classes = cn(m.name ? 'lc' : 'c', {
@@ -382,6 +434,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
if (this.props.modules instanceof Array) {
return;
}
const mountedModule = this.props.m;
return (
<FuzzySearch
list={this.state.fuzzy}
@@ -393,11 +446,20 @@ export default class AvailableModulesMenu extends TranslatedComponent {
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={'lc'}
onClick={() => clickHandler(i)}
className={cn('lc', {disabled})}
onClick={handler}
>
{val.name}
</div>
@@ -516,6 +578,15 @@ export default class AvailableModulesMenu extends TranslatedComponent {
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)
if (a.rating < b.rating) {
return -1;

View File

@@ -32,7 +32,7 @@ export default class CostSection extends TranslatedComponent {
this._buildRetrofitShip = this._buildRetrofitShip.bind(this);
this._onBaseRetrofitChange = this._onBaseRetrofitChange.bind(this);
this._defaultRetrofitName = this._defaultRetrofitName.bind(this);
this._eddbShoppingList = this._eddbShoppingList.bind(this);
this._eddbShoppingList = this._inaraShoppingList.bind(this);
let data = Ships[props.ship.id]; // Retrieve the basic ship properties, slots and defaults
let retrofitName = this._defaultRetrofitName(props.ship.id, props.buildName);
@@ -306,8 +306,8 @@ export default class CostSection extends TranslatedComponent {
<tr className='main'>
<th colSpan='2' className='sortable le' onClick={this._sortCostBy.bind(this,'m')}>
{translate('module')}
{shipDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct(shipDiscount)}]`}</u> : null}
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
{shipDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} ${formats.pct(-1 * shipDiscount)}]`}</u> : null}
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} ${formats.pct(-1 * moduleDiscount)}]`}</u> : null}
</th>
<th className='sortable le' onClick={this._sortCostBy.bind(this, 'cr')} >{translate('credits')}</th>
</tr>
@@ -328,9 +328,9 @@ export default class CostSection extends TranslatedComponent {
}
/**
* Open up a window for EDDB with a shopping list of our retrofit components
* Open up a window for inara with a shopping list of our retrofit components
*/
_eddbShoppingList() {
_inaraShoppingList() {
const { retrofitCosts } = this.state;
const { ship } = this.props;
@@ -338,7 +338,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);
// Open up the relevant URL
window.open('https://eddb.io/station?m=' + modIds.join(','));
window.open('https://inara.cz/inapi/corisearch.php?m=' + modIds.join(','));
}
/**
@@ -387,7 +387,7 @@ export default class CostSection extends TranslatedComponent {
<tbody>
{rows}
<tr className='ri'>
<td className='lbl' ><button onClick={this._eddbShoppingList} onMouseOver={termtip.bind(null, 'PHRASE_REFIT_SHOPPING_LIST')} onMouseOut={tooltip.bind(null, null)}><ShoppingIcon className='lg' style={{ fill: 'black' }}/></button></td>
<td className='lbl' ><button onClick={this._inaraShoppingList} 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='2' className={cn('val', retrofitTotal > 0 ? 'warning' : 'secondary-disabled')} style={{ borderBottom:'none' }}>
{int(retrofitTotal)}{units.CR}

View File

@@ -104,10 +104,10 @@ export default class HardpointSlot extends Slot {
onMouseOut={tooltip.bind(null, null)}>{translate('shotdmg')}: {formats.round1(m.getDamage())}</div> : null}
{m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'epsseps' : 'eps')}
onMouseOut={tooltip.bind(null, null)}>{translate('EPS')}: {formats.round1(m.getEps())}{u.MW} {m.getClip() ?
<span>({formats.round1((m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()))}{u.MW})</span> : null}</div> : null}
<span>({formats.round1(m.getEps() * m.getSustainedFactor())}{u.MW})</span> : null}</div> : null}
{m.getHps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'hpsshps' : 'hps')}
onMouseOut={tooltip.bind(null, null)}>{translate('HPS')}: {formats.round1(m.getHps())} {m.getClip() ?
<span>({formats.round1((m.getClip() * m.getHps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()))})</span> : null}</div> : null}
<span>({formats.round1(m.getHps() * m.getSustainedFactor())})</span> : null}</div> : null}
{m.getDps() && m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, 'dpe')}
onMouseOut={tooltip.bind(null, null)}>{translate('DPE')}: {formats.f1(m.getDps() / m.getEps())}</div> : null}
{m.getRoF() ? <div className={'l'} onMouseOver={termtip.bind(null, 'rof')}
@@ -136,6 +136,7 @@ export default class HardpointSlot extends Slot {
{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)}>

View File

@@ -149,14 +149,14 @@ export default class HardpointSlotSection extends SlotSection {
<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>
</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>
<div className='select-group cap'>{translate('nl')}</div>
<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>
</ul>
<div className='select-group cap'>{translate('ggc')}</div>
<ul>
<li className='lc' tabIndex='0' onClick={_fill.bind(this, 'ggc', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ggc-F'] = smRef}>{translate('ggc')}</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>

View File

@@ -10,7 +10,6 @@ import { Ships } from 'coriolis-data/dist';
import Persist from '../stores/Persist';
import { toDetailedExport } from '../shipyard/Serializer';
import Ship from '../shipyard/Ship';
import ModalBatchOrbis from './ModalBatchOrbis';
import ModalDeleteAll from './ModalDeleteAll';
import ModalExport from './ModalExport';
import ModalHelp from './ModalHelp';
@@ -241,43 +240,6 @@ export default class Header extends TranslatedComponent {
/>);
};
/**
* Uploads all ship-builds to orbis
* @param {e} e Event
*/
_uploadAllBuildsToOrbis(e) {
e.preventDefault();
const data = Persist.getBuilds();
let postObject = [];
for (const ship in data) {
for (const code in data[ship]) {
const shipModel = ship;
if (!shipModel) {
throw 'No such ship found: "' + ship + '"';
}
const shipTemplate = Ships[shipModel];
const shipPostObject = {};
let shipInstance = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots);
shipInstance.buildWith(null);
shipInstance.buildFrom(data[ship][code]);
shipPostObject.coriolisId = shipInstance.id;
shipPostObject.coriolisShip = shipInstance;
shipPostObject.coriolisShip.url = window.location.origin + outfitURL(shipModel, data[ship][code], code);
shipPostObject.title = code || shipInstance.id;
shipPostObject.description = code || shipInstance.id;
shipPostObject.ShipName = shipInstance.id;
shipPostObject.Ship = shipInstance.id;
postObject.push(shipPostObject);
}
}
console.log(postObject);
this.context.showModal(<ModalBatchOrbis
ships={postObject}
/>);
}
/**
* Show export modal with detailed export
* @param {SyntheticEvent} e Event
@@ -426,7 +388,10 @@ export default class Header extends TranslatedComponent {
if (this.props.announcements) {
announcements = [];
for (let announce of this.props.announcements) {
announcements.push(<Announcement text={announce.message} />);
if (announce.expiry < Date.now()) {
continue;
}
announcements.push(<Announcement text={announce.text} />);
announcements.push(<hr/>);
}
}
@@ -496,7 +461,6 @@ export default class Header extends TranslatedComponent {
{translate('builds')} & {translate('comparisons')}
<li><Link href="#" className='block' onClick={this._showBackup.bind(this)}>{translate('backup')}</Link></li>
<li><Link href="#" className='block' onClick={this._showDetailedExport.bind(this)}>{translate('detailed export')}</Link></li>
<li><Link href="#" className='block' onClick={this._uploadAllBuildsToOrbis.bind(this)}>{translate('upload all builds to orbis')}</Link></li>
<li><Link href="#" className='block' onClick={this._showImport.bind(this)}>{translate('import')}</Link></li>
<li><Link href="#" className='block' onClick={this._showDeleteAll.bind(this)}>{translate('delete all')}</Link></li>
</ul>

View File

@@ -78,7 +78,6 @@ export default class InternalSlot extends Slot {
{ m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
{ m.rangeLS === null ? <div className={'l'}>{u.Ls}</div> : null }
{ m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</div> : null }
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
{ m.passengers ? <div className={'l'}>{translate('passengers')}: {m.passengers}</div> : null }
{ m.getRegenerationRate() ? <div className='l'>{translate('regen')}: {formats.round1(m.getRegenerationRate())}{u.ps}</div> : null }
{ m.getBrokenRegenerationRate() ? <div className='l'>{translate('brokenregen')}: {formats.round1(m.getBrokenRegenerationRate())}{u.ps}</div> : null }
@@ -89,6 +88,7 @@ export default class InternalSlot extends Slot {
{ m.getHullReinforcement() ? <div className='l'>{translate('armour')}: {formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)}</div> : null }
{ m.getProtection() ? <div className='l'>{translate('protection')}: {formats.rPct(m.getProtection())}</div> : null }
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
{ m.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>;

View File

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

View File

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

View File

@@ -11,7 +11,10 @@ import * as ModuleUtils from '../shipyard/ModuleUtils';
import { fromDetailedBuild } from '../shipyard/Serializer';
import { Download } from './SvgIcons';
import { outfitURL } from '../utils/UrlGenerators';
import * as CompanionApiUtils from '../utils/CompanionApiUtils';
import { shipFromJson, shipModelFromJson } from '../utils/CompanionApiUtils';
import { shipFromLoadoutJSON } from '../utils/JournalUtils';
const zlib = require('pako');
const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n');
const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)');
@@ -86,6 +89,7 @@ export default class ModalImport extends TranslatedComponent {
static propTypes = {
importString: PropTypes.string, // Optional: Default data for import modal
builds: PropTypes.object, // Optional: Import object
};
@@ -99,11 +103,12 @@ export default class ModalImport extends TranslatedComponent {
this.state = {
builds: props.builds,
canEdit: !props.builds,
loadoutEvent: null,
comparisons: null,
shipDiscount: null,
moduleDiscount: null,
errorMsg: null,
importString: null,
importString: props.importString || null,
importValid: false,
insurance: null
};
@@ -111,12 +116,28 @@ export default class ModalImport extends TranslatedComponent {
this._process = this._process.bind(this);
this._import = this._import.bind(this);
this._importBackup = this._importBackup.bind(this);
this._importLoadout = this._importLoadout.bind(this);
this._importDetailedArray = this._importDetailedArray.bind(this);
this._importTextBuild = this._importTextBuild.bind(this);
this._importCompanionApiBuild = this._importCompanionApiBuild.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
* @param {Object} importData Backup Data
@@ -159,7 +180,7 @@ export default class ModalImport extends TranslatedComponent {
}
// Check for module discount
if (!isNaN(importData.moduleDiscount)) {
this.setState({ shipDiscount: importData.moduleDiscount * 1 });
this.setState({ moduleDiscount: importData.moduleDiscount * 1 });
}
if (typeof importData.insurance == 'string') {
@@ -195,8 +216,8 @@ export default class ModalImport extends TranslatedComponent {
* @throws {string} if parse/import fails
*/
_importCompanionApiBuild(build) {
const shipModel = CompanionApiUtils.shipModelFromJson(build);
const ship = CompanionApiUtils.shipFromJson(build);
const shipModel = shipModelFromJson(build);
const ship = shipFromJson(build);
let builds = {};
builds[shipModel] = {};
@@ -302,6 +323,30 @@ export default class ModalImport extends TranslatedComponent {
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
* @param {SyntheticEvent} event Event
@@ -336,8 +381,10 @@ export default class ModalImport extends TranslatedComponent {
throw 'Must be an object or array!';
}
if (importData.modules != null && importData.modules.Armour != null) { // Only the companion API has this information
this._importCompanionApiBuild(importData); // Single sihp definition
if (importData?.[0]?.header?.appName) { // has SLEF envelope?
this._importSlefBuilds(importData);
} 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
this._importCompanionApiBuild(importData.ship); // Complete API dump
} else if (importData instanceof Array) { // Must be detailed export json
@@ -345,12 +392,14 @@ export default class ModalImport extends TranslatedComponent {
} 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.setState({ singleBuild: true });
} else if (importData.Modules != null && importData.Modules[0] != null) {
this._importLoadout(importData);
} else { // Using Backup JSON
this._importBackup(importData);
}
}
} catch (e) {
// console.log(e.stack);
console.log(e);
this.setState({ errorMsg: (typeof e == 'string') ? e : 'Cannot Parse the data!' });
return;
}
@@ -364,6 +413,10 @@ export default class ModalImport extends TranslatedComponent {
_process() {
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 (this.state.singleBuild) {
builds = this.state.builds;
@@ -480,7 +533,7 @@ export default class ModalImport extends TranslatedComponent {
if (!state.processed) {
importStage = (
<div>
<textarea className='cb json' ref={node => this.importField = node} onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
<textarea spellCheck={false} className='cb json' ref={node => this.importField = node} onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
<button id='proceed' className='l cap' onClick={this._process} disabled={!state.importValid} >{translate('proceed')}</button>
<div className='l warning' style={{ marginLeft:'3em' }}>{state.errorMsg}</div>
</div>
@@ -517,7 +570,7 @@ export default class ModalImport extends TranslatedComponent {
{comparisonRows}
</tbody>
</table>
);
);
}
if(this.state.canEdit) {

View File

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

View File

@@ -3,6 +3,8 @@ 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
@@ -56,7 +58,6 @@ export default class ModalShoppingList extends TranslatedComponent {
continue;
}
if (module.m.blueprint.special) {
console.log(module.m.blueprint.special);
blueprints.push({ uuid: module.m.blueprint.special.uuid, number: 1 });
}
for (const g in module.m.blueprint.grades) {
@@ -146,6 +147,64 @@ export default class ModalShoppingList extends TranslatedComponent {
}
}
/**
* 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
*/
@@ -177,6 +236,18 @@ export default class ModalShoppingList extends TranslatedComponent {
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];
}
}
}
}
}
}
@@ -229,6 +300,7 @@ export default class ModalShoppingList extends TranslatedComponent {
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>
@@ -257,6 +329,7 @@ export default class ModalShoppingList extends TranslatedComponent {
<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,7 +3,8 @@ import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames';
import NumberEditor from 'react-number-editor';
import { isValueBeneficial } from '../utils/BlueprintFunctions';
import { isChangeValueBeneficial } from '../utils/BlueprintFunctions';
import { Modifications } from 'coriolis-data/dist';
/**
* Modification
@@ -43,7 +44,7 @@ export default class Modification extends TranslatedComponent {
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, true);
ship.setModification(m, name, value, true);
}
}
@@ -79,6 +80,7 @@ export default class Modification extends TranslatedComponent {
let { translate, formats, units } = this.context.language;
let { m, name } = this.props;
let modValue = m.getChange(name);
let isOverwrite = Modifications.modifications[name].method === 'overwrite';
if (name === 'damagedist') {
// We don't show damage distribution
@@ -117,10 +119,10 @@ export default class Modification extends TranslatedComponent {
</td>
<td style={{ textAlign: 'center' }} className={
modValue ?
isValueBeneficial(name, modValue) ? 'secondary' : 'warning' :
isChangeValueBeneficial(name, modValue) ? 'secondary' : 'warning' :
''
}>
{formats.f2(modValue / 100) || 0}%
{formats.f2(modValue / 100) || 0}{isOverwrite ? '' : '%'}
</td>
</tr>
</tbody>

View File

@@ -436,7 +436,7 @@ export default class ModificationsMenu extends TranslatedComponent {
let specialLabel;
let specialTt;
if (m.blueprint && m.blueprint.special) {
specialLabel = m.blueprint.special.name;
specialLabel = translate(m.blueprint.special.name);
specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, m.blueprint.special.edname);
} else {
specialLabel = translate('PHRASE_SELECT_SPECIAL');

View File

@@ -243,8 +243,13 @@ export default class Offence extends TranslatedComponent {
<td className='ri'><span onMouseOver={termtip.bind(null, baseSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.base.total)}</span></td>
<td className='ri'><span onMouseOver={termtip.bind(null, effectiveShieldsSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.shields.total)}</span></td>
<td className='ri'><span onMouseOver={termtip.bind(null, effectivenessShieldsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.shields.total)}</span></td>
<td className='ri'><span>{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, 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>);
}
@@ -271,15 +276,20 @@ export default class Offence extends TranslatedComponent {
<tr className='main'>
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</th>
<th colSpan='1'>{translate('overall')}</th>
<th colSpan='2'>{translate('opponent\'s shields')}</th>
<th colSpan='2'>{translate('opponent\'s armour')}</th>
<th colSpan='3'>{translate('opponent\'s shields')}</th>
<th colSpan='3'>{translate('opponent\'s armour')}</th>
</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='sortable'>{'dpe'}</th>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'esdpsh')}>{'sdps'}</th>
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'eh')}>{'eft'}</th>
<th className='sortable'>{'dpe'}</th>
</tr>
</thead>
<tbody>
@@ -290,8 +300,10 @@ export default class Offence extends TranslatedComponent {
<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>

View File

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

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Pip } from './SvgIcons';
import { autoBind } from 'react-extras';
import autoBind from 'auto-bind';
/**
* Pips displays SYS/ENG/WEP pips and allows users to change them with key presses by clicking on the relevant area.

View File

@@ -50,6 +50,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const canBoost = ship.canBoost(cargo, ship.fuelCapacity);
const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const 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);
@@ -71,7 +72,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
<tr className='main'>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
<th colSpan={5}>{translate('jump range')}</th>
<th colSpan={5} className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('jump range')}</th>
<th rowSpan={2}>{translate('shield')}</th>
<th rowSpan={2}>{translate('integrity')}</th>
<th rowSpan={2}>{translate('DPS')}</th>
@@ -85,15 +86,15 @@ export default class ShipSummaryTable extends TranslatedComponent {
<th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
<th rowSpan={2}>{translate('crew')}</th>
<th onMouseEnter={termtip.bind(null, 'mass lock factor', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_BOOST_TIME', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('boost time')}</th>
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_BOOST_INTERVAL', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('boost interval')}</th>
<th rowSpan={2}>{translate('resting heat (Beta)')}</th>
</tr>
<tr>
<th className='lft'>{translate('max')}</th>
<th>{translate('unladen')}</th>
<th>{translate('laden')}</th>
<th>{translate('total unladen')}</th>
<th>{translate('total laden')}</th>
<th className={ cn({ 'lft': true, 'bg-warning-disabled': !canJump }) }>{translate('max')}</th>
<th className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('unladen')}</th>
<th className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('laden')}</th>
<th className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('total unladen')}</th>
<th className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('total laden')}</th>
<th className='lft'>{translate('hull')}</th>
<th>{translate('unladen')}</th>
<th>{translate('laden')}</th>
@@ -103,11 +104,11 @@ export default class ShipSummaryTable extends TranslatedComponent {
<tr>
<td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })} onMouseLeave={hide}>{ canThrust ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, false))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td onMouseEnter={termtip.bind(null, 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><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.standard[2].m.getMaxFuelPerJump(), ship.standard[2].m, ship.standard[2].m.getMaxFuelPerJump(), ship))}{u.LY}</span></td>
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span></td>
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span></td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_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 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_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_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_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 className={sgClassNames} onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })} onMouseLeave={hide}>{int(ship.shield)}{u.MJ}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })} onMouseLeave={hide}>{int(ship.armour)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalDps)}</td>
@@ -159,10 +160,10 @@ export default class ShipSummaryTable extends TranslatedComponent {
<td>{formats.pct1(ship.shieldThermRes)}</td>
<td></td>
<td>{int(ship && ship.shield > 0 ? ship.shield : 0)}{u.MJ}</td>
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldExplRes)))) : 0)}{u.MJ}</td>
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldKinRes)))) : 0)}{u.MJ}</td>
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldThermRes)))) : 0)}{u.MJ}</td>
<td>{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>
@@ -197,11 +198,11 @@ export default class ShipSummaryTable extends TranslatedComponent {
<td>{formats.pct1(ship.hullKinRes)}</td>
<td>{formats.pct1(ship.hullThermRes)}</td>
<td>{formats.pct1(ship.hullCausRes)}</td>
<td>{int(ship.armour)}</td>
<td>{int(ship.armour * ((1 / (1 - (ship.hullExplRes)))))}</td>
<td>{int(ship.armour * ((1 / (1 - (ship.hullKinRes)))))}</td>
<td>{int(ship.armour * ((1 / (1 - (ship.hullThermRes)))))}</td>
<td>{int(ship.armour * ((1 / (1 - (ship.hullCausRes)))))}</td>
<td>{int(armourMetrics.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>

View File

@@ -1,386 +1,386 @@
import React from 'react';
import PropTypes from 'prop-types';
const MARGIN_LR = 8; // Left/ Right margin
/**
* Horizontal Slider
*/
export default class Slider extends React.Component {
static defaultProps = {
axis: false,
min: 0,
max: 1,
scale: 1 // SVG render scale
};
static propTypes = {
axis: PropTypes.bool,
axisUnit: PropTypes.string,// units (T, M, etc.)
max: PropTypes.number,
min: PropTypes.number,
onChange: PropTypes.func.isRequired,// function which determins percent value
onResize: PropTypes.func,
percent: PropTypes.number.isRequired,// value of slider
scale: PropTypes.number
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this._down = this._down.bind(this);
this._move = this._move.bind(this);
this._up = this._up.bind(this);
this._keyup = this._keyup.bind(this);
this._keydown = this._keydown.bind(this);
this._touchstart = this._touchstart.bind(this);
this._touchend = this._touchend.bind(this);
this._updatePercent = this._updatePercent.bind(this);
this._updateDimensions = this._updateDimensions.bind(this);
this.state = { width: 0 };
}
/**
* On Mouse/Touch down handler
* @param {SyntheticEvent} event Event
*/
_down(event) {
let rect = event.currentTarget.getBoundingClientRect();
this.left = rect.left;
this.width = rect.width;
this._move(event);
this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500);
}
/**
* Update the slider percentage on move
* @param {SyntheticEvent} event Event
*/
_move(event) {
if(this.width !== null && this.left != null) {
let clientX = event.touches ? event.touches[0].clientX : event.clientX;
event.preventDefault();
this._updatePercent(clientX - this.left, this.width);
}
}
/**
* On Mouse/Touch up handler
* @param {Event} event DOM Event
*/
_up(event) {
this.sliderInputBox.sliderVal.focus();
clearTimeout(this.touchStartTimer);
event.preventDefault();
this.left = null;
this.width = null;
}
/**
* Key up handler for keyboard.
* display the number field then set focus to it
* when "Enter" key is pressed
* @param {Event} event Keyboard event
*/
_keyup(event) {
switch (event.key) {
case 'Enter':
event.preventDefault();
this.sliderInputBox._setDisplay('block');
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
* @param {SyntheticEvent} event Event
*/
_enter(event) {
if(event.buttons !== 1) {
this.left = null;
this.width = null;
}
}
/**
* Update the slider percentage
* @param {number} pos Slider drag position
* @param {number} width Slider width
* @param {Event} event DOM Event
*/
_updatePercent(pos, width) {
this.props.onChange(Math.min(Math.max(pos / width, 0), 1));
}
/**
* Update dimenions from rendered DOM
*/
_updateDimensions() {
this.setState({
outerWidth: this.node.getBoundingClientRect().width
});
}
/**
* Add listeners when about to mount
*/
componentWillMount() {
if (this.props.onResize) {
this.resizeListener = this.props.onResize(this._updateDimensions);
}
}
/**
* Trigger DOM updates on mount
*/
componentDidMount() {
this._updateDimensions();
}
/**
* Remove listeners on unmount
*/
componentWillUnmount() {
if (this.resizeListener) {
this.resizeListener.remove();
}
}
/**
* Render the slider
* @return {React.Component} The slider
*/
render() {
let outerWidth = this.state.outerWidth;
let { axis, axisUnit, min, max, scale } = this.props;
let style = {
width: '100%',
height: axis ? '2.5em' : '1.5em',
boxSizing: 'border-box'
};
if (!outerWidth) {
return <svg style={style} ref={node => this.node = node} />;
}
let margin = MARGIN_LR * scale;
let width = outerWidth - (margin * 2);
let pctPos = width * this.props.percent;
return <div><svg
onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onKeyUp={this._keyup} onKeyDown={this._keydown} style={style} ref={node => this.node = node} tabIndex="0">
<rect className='primary' style={{ opacity: 0.3 }} x={margin} y='0.25em' rx='0.3em' ry='0.3em' width={width} height='0.7em' />
<rect className='primary-disabled' x={margin} y='0.45em' rx='0.15em' ry='0.15em' width={pctPos} height='0.3em' />
<circle className='primary' r={margin} cy='0.6em' cx={pctPos + margin} />
<rect x={margin} width={width} height='100%' fillOpacity='0' style={{ cursor: 'col-resize' }} onMouseDown={this._down} onTouchMove={this._move} onTouchStart={this._down} onTouchEnd={this._touchend} />
{axis && <g style={{ fontSize: '.7em' }}>
<text className='primary-disabled' y='3em' x={margin} style={{ textAnchor: 'middle' }}>{min + axisUnit}</text>
<text className='primary-disabled' y='3em' x='50%' style={{ textAnchor: 'middle' }}>{(min + max / 2) + axisUnit}</text>
<text className='primary-disabled' y='3em' x='100%' style={{ textAnchor: 'end' }}>{max + axisUnit}</text>
</g>}
</svg>
<TextInputBox ref={(tb) => this.sliderInputBox = tb}
onChange={this.props.onChange}
percent={this.props.percent}
axisUnit={this.props.axisUnit}
scale={this.props.scale}
max={this.props.max}
/>
</div>;
}
}
/**
* New component to add keyboard support for sliders - works on all devices (desktop, iOS, Android)
**/
class TextInputBox extends React.Component {
static propTypes = {
axisUnit: PropTypes.string,// units (T, M, etc.)
max: PropTypes.number,
onChange: PropTypes.func.isRequired,// function which determins percent value
percent: PropTypes.number.isRequired,// value of slider
scale: PropTypes.number
};
/**
* 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>;
}
}
import React from 'react';
import PropTypes from 'prop-types';
const MARGIN_LR = 8; // Left/ Right margin
/**
* Horizontal Slider
*/
export default class Slider extends React.Component {
static defaultProps = {
axis: false,
min: 0,
max: 1,
scale: 1 // SVG render scale
};
static propTypes = {
axis: PropTypes.bool,
axisUnit: PropTypes.string,// units (T, M, etc.)
max: PropTypes.number,
min: PropTypes.number,
onChange: PropTypes.func.isRequired,// function which determins percent value
onResize: PropTypes.func,
percent: PropTypes.number.isRequired,// value of slider
scale: PropTypes.number
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this._down = this._down.bind(this);
this._move = this._move.bind(this);
this._up = this._up.bind(this);
this._keyup = this._keyup.bind(this);
this._keydown = this._keydown.bind(this);
this._touchstart = this._touchstart.bind(this);
this._touchend = this._touchend.bind(this);
this._updatePercent = this._updatePercent.bind(this);
this._updateDimensions = this._updateDimensions.bind(this);
this.state = { width: 0 };
}
/**
* On Mouse/Touch down handler
* @param {SyntheticEvent} event Event
*/
_down(event) {
let rect = event.currentTarget.getBoundingClientRect();
this.left = rect.left;
this.width = rect.width;
this._move(event);
this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500);
}
/**
* Update the slider percentage on move
* @param {SyntheticEvent} event Event
*/
_move(event) {
if(this.width !== null && this.left != null) {
let clientX = event.touches ? event.touches[0].clientX : event.clientX;
event.preventDefault();
this._updatePercent(clientX - this.left, this.width);
}
}
/**
* On Mouse/Touch up handler
* @param {Event} event DOM Event
*/
_up(event) {
this.sliderInputBox.sliderVal.focus();
clearTimeout(this.touchStartTimer);
event.preventDefault();
this.left = null;
this.width = null;
}
/**
* Key up handler for keyboard.
* display the number field then set focus to it
* when "Enter" key is pressed
* @param {Event} event Keyboard event
*/
_keyup(event) {
switch (event.key) {
case 'Enter':
event.preventDefault();
this.sliderInputBox._setDisplay('block');
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
* @param {SyntheticEvent} event Event
*/
_enter(event) {
if(event.buttons !== 1) {
this.left = null;
this.width = null;
}
}
/**
* Update the slider percentage
* @param {number} pos Slider drag position
* @param {number} width Slider width
* @param {Event} event DOM Event
*/
_updatePercent(pos, width) {
this.props.onChange(Math.min(Math.max(pos / width, 0), 1));
}
/**
* Update dimenions from rendered DOM
*/
_updateDimensions() {
this.setState({
outerWidth: this.node.getBoundingClientRect().width
});
}
/**
* Add listeners when about to mount
*/
componentWillMount() {
if (this.props.onResize) {
this.resizeListener = this.props.onResize(this._updateDimensions);
}
}
/**
* Trigger DOM updates on mount
*/
componentDidMount() {
this._updateDimensions();
}
/**
* Remove listeners on unmount
*/
componentWillUnmount() {
if (this.resizeListener) {
this.resizeListener.remove();
}
}
/**
* Render the slider
* @return {React.Component} The slider
*/
render() {
let outerWidth = this.state.outerWidth;
let { axis, axisUnit, min, max, scale } = this.props;
let style = {
width: '100%',
height: axis ? '2.5em' : '1.5em',
boxSizing: 'border-box'
};
if (!outerWidth) {
return <svg style={style} ref={node => this.node = node} />;
}
let margin = MARGIN_LR * scale;
let width = outerWidth - (margin * 2);
let pctPos = width * this.props.percent;
return <div><svg
onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onKeyUp={this._keyup} onKeyDown={this._keydown} style={style} ref={node => this.node = node} tabIndex="0">
<rect className='primary' style={{ opacity: 0.3 }} x={margin} y='0.25em' rx='0.3em' ry='0.3em' width={width} height='0.7em' />
<rect className='primary-disabled' x={margin} y='0.45em' rx='0.15em' ry='0.15em' width={pctPos} height='0.3em' />
<circle className='primary' r={margin} cy='0.6em' cx={pctPos + margin} />
<rect x={margin} width={width} height='100%' fillOpacity='0' style={{ cursor: 'col-resize' }} onMouseDown={this._down} onTouchMove={this._move} onTouchStart={this._down} onTouchEnd={this._touchend} />
{axis && <g style={{ fontSize: '.7em' }}>
<text className='primary-disabled' y='3em' x={margin} style={{ textAnchor: 'middle' }}>{min + axisUnit}</text>
<text className='primary-disabled' y='3em' x='50%' style={{ textAnchor: 'middle' }}>{(min + max / 2) + axisUnit}</text>
<text className='primary-disabled' y='3em' x='100%' style={{ textAnchor: 'end' }}>{max + axisUnit}</text>
</g>}
</svg>
<TextInputBox ref={(tb) => this.sliderInputBox = tb}
onChange={this.props.onChange}
percent={this.props.percent}
axisUnit={this.props.axisUnit}
scale={this.props.scale}
max={this.props.max}
/>
</div>;
}
}
/**
* New component to add keyboard support for sliders - works on all devices (desktop, iOS, Android)
**/
class TextInputBox extends React.Component {
static propTypes = {
axisUnit: PropTypes.string,// units (T, M, etc.)
max: PropTypes.number,
onChange: PropTypes.func.isRequired,// function which determins percent value
percent: PropTypes.number.isRequired,// value of slider
scale: PropTypes.number
};
/**
* 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

@@ -99,6 +99,7 @@ export default class Slot extends TranslatedComponent {
let translate = language.translate;
let { ship, m, enabled, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props;
let slotDetails, modificationsMarker, menu;
let missing = false;
if (!selected) {
// If not selected then sure that modifications flag is unset
@@ -108,6 +109,11 @@ export default class Slot extends TranslatedComponent {
if (m) {
slotDetails = this._getSlotDetails(m, enabled, translate, language.formats, language.units); // Must be implemented by sub classes
modificationsMarker = JSON.stringify(m);
if(typeof m.grp !== 'undefined' || m.grp !== null) {
if(m.grp == "mh" || m.grp == "mm") {
missing = true;
}
}
} else {
slotDetails = <div className={'empty'}>{translate(eligible ? 'emptyrestricted' : 'empty')}</div>;
modificationsMarker = '';
@@ -127,8 +133,8 @@ export default class Slot extends TranslatedComponent {
menu = <AvailableModulesMenu
className={this._getClassNames()}
modules={availableModules()}
shipMass={ship.hullMass}
m={m}
ship={ship}
onSelect={onSelect}
warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)}
@@ -138,13 +144,16 @@ export default class Slot extends TranslatedComponent {
}
// TODO: implement touch dragging
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='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>
{slotDetails}
</div>
</div>
{menu}
</div>
);

View File

@@ -195,6 +195,15 @@ export default class SlotSection extends TranslatedComponent {
if (targetSlot && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
const mCopy = m.clone();
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
targetSlot.enabled = originSlot.enabled;
targetSlot.priority = originSlot.priority;

View File

@@ -93,6 +93,11 @@ export default class StandardSlot extends TranslatedComponent {
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);
if (selected) {
@@ -109,8 +114,8 @@ export default class StandardSlot extends TranslatedComponent {
menu = <AvailableModulesMenu
className='standard'
modules={modules}
shipMass={ModuleUtils.isShieldGenerator(m.grp) ? ship.hullMass : ship.unladenMass}
m={m}
ship={ship}
onSelect={onSelect}
warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)}
@@ -124,7 +129,7 @@ export default class StandardSlot extends TranslatedComponent {
<div className={cn('details-container', { warning: warning && warning(slot.m), disabled: m.grp !== 'bh' && !slot.enabled })}>
<div className={'sz'}>{m.grp == 'bh' ? m.name.charAt(0) : slot.maxClass}</div>
<div>
<div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }</div>
<div className={'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={'r'}>{formats.round(mass)}{units.T}</div>
<div/>
<div className={'cb'}>
@@ -144,7 +149,8 @@ export default class StandardSlot extends TranslatedComponent {
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
{ validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
{ m.getInfo() ? <div className='l'>{translate(m.getInfo())}</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>

View File

@@ -247,7 +247,8 @@ export class OrbisIcon extends SvgIcon {
<path d="m155.34 679.12 173.25-190.21-15.626-13.721-170.9 190.4zm31.01 31.714 202.41-169.1-16.418-14.417-198.76 170.43z"/>
<path d="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>);
</g>
);
}
}

View File

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

View File

@@ -8,6 +8,7 @@ import * as RU from './ru';
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';
let fallbackTerms = EN.terms;
@@ -29,6 +30,7 @@ export function getLanguage(langCode) {
case 'pl': lang = PL; break;
case 'pt': lang = PT; break;
case 'cn': lang = CN; break;
case 'ko': lang = KO; break;
default:
lang = EN;
}
@@ -97,5 +99,6 @@ export const Languages = {
ru: 'ру́сский',
pl: 'polski',
pt: 'português',
cn: '中文'
cn: '中文',
ko: '한국어'
};

View File

@@ -61,7 +61,7 @@
"TT_SUMMARY_SPEED": "给推进器分配4格能量值且满燃料",
"TT_SUMMARY_SPEED_NONFUNCTIONAL": "推进器被关闭或者飞船满燃料满载时的总质量超出推进器最大可推进质量",
"TT_SUMMARY_BOOST": "给推进器分配4格能量值且满燃料",
"TT_SUMMARY_BOOST_TIME": "给推进器分配4格能量值时每次助推间隔时间",
"TT_SUMMARY_BOOST_INTERVAL": "给推进器分配4格能量值时每次助推间隔时间",
"TT_SUMMARY_BOOST_NONFUNCTIONAL": "配电器无法提供足够的能量以供助推",
"TT_SUMMARY_SHIELDS": "护盾的绝对值,包含增幅装置",
"TT_SUMMARY_SHIELDS_SCB": "护盾的绝对值,包含增幅装置和护盾电池",
@@ -157,7 +157,7 @@
"shield": "护盾",
"integrity": "强度",
"mass": "质量",
"boost time": "推进时间",
"boost interval": "推进时间",
"resting heat (Beta)": "待机热量(测试)",
"No Shield": "无护盾",
"resistance": "抗性",
@@ -366,7 +366,7 @@
"permalink": "分享",
"URL": "链接",
"shortened": "短链接",
"fastest range": "最长距离",
"farthest range": "最长距离",
"Sustained DPS": "持续DPS",
"build": "配置名",
"thrusters": "推进器",
@@ -398,6 +398,7 @@
"No modded components.": "没有改装的部件",
"Sending...": "发送中...",
"Send to EDEngineer": "发送至EDEngineer",
"Send to EDOMH": "发送至EDOMH",
"PHASE_UPLOAD_ORBIS": "上传到orbis.zone测试阶段",
"orbis username": "orbis.zone的Email或用户名",
"orbis password": "orbis.zone的密码",

View File

@@ -1,16 +1,16 @@
export const formats = {
decimal: ',',
thousands: '.',
grouping: [3],
currency: ['', ' €'],
dateTime: '%A, der %e. %B %Y, %X',
date: '%d.%m.%Y',
time: '%H:%M:%S',
periods: ['AM', 'PM'], // unused
days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
shortDays: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
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']
};
export { default as terms } from './de.json';
export const formats = {
decimal: ',',
thousands: '.',
grouping: [3],
currency: ['', ' €'],
dateTime: '%A, der %e. %B %Y, %X',
date: '%d.%m.%Y',
time: '%H:%M:%S',
periods: ['AM', 'PM'], // unused
days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
shortDays: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
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']
};
export { default as terms } from './de.json';

File diff suppressed because one or more lines are too long

View File

@@ -63,7 +63,7 @@
"TT_SUMMARY_SPEED": "With full fuel tank and 4 pips to ENG",
"TT_SUMMARY_SPEED_NONFUNCTIONAL": "Thrusters powered off or over maximum mass with full fuel and cargo loads",
"TT_SUMMARY_BOOST": "With full fuel tank and 4 pips to ENG",
"TT_SUMMARY_BOOST_TIME": "Time between each boost with 4 pips to ENG",
"TT_SUMMARY_BOOST_INTERVAL": "Time between each boost with 4 pips to ENG",
"TT_SUMMARY_BOOST_NONFUNCTIONAL": "Power distributor not able to supply enough power to boost",
"TT_SUMMARY_SHIELDS": "Raw shield strength, including boosters",
"TT_SUMMARY_SHIELDS_SCB": "Raw shield strength, including boosters and SCBs",
@@ -83,6 +83,7 @@
"HELP_MODIFICATIONS_MENU": "Click on a number to enter a new value, or drag along the bar for small changes",
"PHRASE_FAIL_EDENGINEER": "Failed to send to EDEngineer (Launch EDEngineer and make sure the API is started then refresh the page.)",
"PHRASE_FIREFOX_EDENGINEER": "Sending to EDEngineer is not compatible with Firefox's security settings. Please try again with Chrome.",
"MISSING_MODULES": "Missing Modules",
"am": "Auto Field-Maintenance Unit",
"bh": "Bulkheads",
"bl": "Beam Laser",
@@ -93,6 +94,7 @@
"ch": "Chaff Launcher",
"cr": "Cargo Rack",
"cs": "Manifest Scanner",
"csl": "Caustic Sink Launcher",
"dc": "Docking Computer",
"ec": "Electronic Countermeasure",
"fc": "Fragment Cannon",
@@ -108,10 +110,18 @@
"kw": "Kill Warrant Scanner",
"ls": "Life Support",
"mc": "Multi-cannon",
"mh": "Missing Weapon/Utility",
"mm": "Missing Module",
"advmc": "Multi-cannon (Advanced)",
"axmc": "AX Multi-cannon",
"axmce": "AX Multi-cannon (Enhanced)",
"ml": "Mining Laser",
"mlc": "Multi Limpet Controller",
"mr": "Missile Rack",
"amr": "Missile Rack (Advanced)",
"axmr": "AX Missile Rack",
"axmre": "AX Missile Rack (Enhanced)",
"ews": "Experimental Weapon Stabilizer",
"mrp": "Module Reinforcement Package",
"nl": "Mine Launcher",
"pa": "Plasma Accelerator",
@@ -153,10 +163,13 @@
"sfn": "Shutdown Field Neutraliser",
"sg": "Shield Generator",
"ss": "Surface Scanners",
"sua": "Supercruise Assist",
"t": "thrusters",
"tp": "Torpedo Pylon",
"ntp": "Nanite Torpedo Pylon",
"ul": "Burst Laser",
"Send To EDEngineer": "Send To EDEngineer",
"Send To EDOMH": "Send To EDOMH",
"ws": "Frame Shift Wake Scanner",
"rpl": "Repair Limpet Controller",
"rcpl": "Recon Limpet Controller",
@@ -204,9 +217,10 @@
"internal protection": "Internal protection",
"external protection": "External protection",
"engagement range": "Engagement range",
"boost time": "Boost time",
"boost interval": "Boost interval",
"total": "Total",
"ammo": "Ammunition maximum",
"info": "Info",
"boot": "Boot time",
"hacktime": "Hack time",
"brokenregen": "Broken regeneration rate",

View File

@@ -59,7 +59,7 @@
"empty all": "vide tout",
"Enter Name": "Entrer nom",
"Explorer": "explorateur",
"fastest range": "gamme la plus rapide",
"farthest range": "gamme la plus rapide",
"fuel": "carburant",
"fuel level": "niveau de carburant",
"full tank": "Réservoir plein",

16
src/app/i18n/ko.js Normal file
View File

@@ -0,0 +1,16 @@
export const formats = {
decimal: '.',
thousands: ',',
grouping: [3],
currency: ['₩', ''],
dateTime: '%a %b %e %X %Y',
date: '%Y/%m/%d',
time: '%H:%M:%S',
periods: ['오전', '오후'],
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';

369
src/app/i18n/ko.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -2,15 +2,15 @@ export const formats = {
decimal: ',',
thousands: '.',
grouping: [3],
currency: ['', ''],
currency: ['$', ''],
dateTime: '%A, %e de %B de %Y, %X',
date: '%d/%m/%Y',
time: '%H:%M:%S',
periods: ['AM', 'PM'],
days: ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'],
shortDays: ['dom', 'lun', 'mar', 'mié', 'jue', 'vie', 'sáb'],
months: ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'],
shortMonths: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic']
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,12 +1,6 @@
import '@babel/polyfill';
import React from 'react';
import { render } from 'react-dom';
import '../less/app.less';
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'));

View File

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

View File

@@ -45,6 +45,9 @@ export default class ErrorDetails extends React.Component {
return <div className='error'>
<h1>Jameson, we have a problem..</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/>
{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/>

View File

@@ -1,5 +1,4 @@
import React from 'react';
// import Perf from 'react-addons-perf';
import { Ships } from 'coriolis-data/dist';
import cn from 'classnames';
import Page from './Page';
@@ -19,7 +18,6 @@ import {
LinkIcon,
ShoppingIcon,
MatIcon,
OrbisIcon
} from '../components/SvgIcons';
import LZString from 'lz-string';
import ShipSummaryTable from '../components/ShipSummaryTable';
@@ -37,7 +35,6 @@ import OutfittingSubpages from '../components/OutfittingSubpages';
import ModalExport from '../components/ModalExport';
import ModalPermalink from '../components/ModalPermalink';
import ModalShoppingList from '../components/ModalShoppingList';
import ModalOrbis from '../components/ModalOrbis';
/**
* Document Title Generator
@@ -60,7 +57,6 @@ export default class OutfittingPage extends Page {
*/
constructor(props, context) {
super(props, context);
// window.Perf = Perf;
this.state = this._initState(props, context);
this._keyDown = this._keyDown.bind(this);
this._exportBuild = this._exportBuild.bind(this);
@@ -681,26 +677,9 @@ export default class OutfittingPage extends Page {
}
/**
* Generate Orbis link
* Open up a window for inara with a shopping list of our components
*/
_genOrbis() {
const data = {};
const ship = this.state.ship;
ship.coriolisId = ship.id;
data.coriolisShip = ship;
data.url = window.location.href;
data.title = this.state.buildName || ship.id;
data.description = this.state.buildName || ship.id;
data.ShipName = ship.id;
data.Ship = ship.id;
console.log(data);
this.context.showModal(<ModalOrbis ship={data} />);
}
/**
* Open up a window for EDDB with a shopping list of our components
*/
_eddbShoppingList() {
_inaraShoppingList() {
const ship = this.state.ship;
const shipId = Ships[ship.id].eddbID;
@@ -713,7 +692,7 @@ export default class OutfittingPage extends Page {
// Open up the relevant URL
window.open(
'https://eddb.io/station?s=' + shipId + '&m=' + modIds.join(',')
'https://inara.cz/inapi/corisearch.php?s=' + shipId + '&m=' + modIds.join(',')
);
}
@@ -933,7 +912,7 @@ export default class OutfittingPage extends Page {
<Download className="lg" />
</button>
<button
onClick={this._eddbShoppingList}
onClick={this._inaraShoppingList}
onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_LIST')}
onMouseOut={hide}
>
@@ -946,13 +925,6 @@ export default class OutfittingPage extends Page {
>
<LinkIcon className="lg" />
</button>
<button
onClick={this._genOrbis}
onMouseOver={termtip.bind(null, 'PHASE_UPLOAD_ORBIS')}
onMouseOut={hide}
>
<OrbisIcon className="lg" />
</button>
<button
onClick={this._genShoppingList}
onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_MATS')}

View File

@@ -50,19 +50,6 @@ export default class Page extends React.Component {
}
}
/**
* 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
*/
shouldComponentUpdate(np, ns, nc) {
return !shallowEqual(this.props, np) || !shallowEqual(this.state, ns) || !shallowEqual(this.context, nc);
}
/**
* Update the window title upon mount
*/
@@ -75,6 +62,14 @@ export default class Page extends React.Component {
*/
componentDidMount() {
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

@@ -126,12 +126,14 @@ export default class ShipyardPage extends Page {
title: 'Coriolis EDCD Edition - Shipyard',
shipPredicate: 'name',
shipDesc: true,
shipSummaries: ShipyardPage.cachedShipSummaries
shipSummaries: ShipyardPage.cachedShipSummaries,
compare: {},
groupCompared: false,
};
}
/**
* Higlight the current ship in the table
* Higlight the current ship in the table on mouse over
* @param {String} shipId Ship Id
* @param {SyntheticEvent} event Event
*/
@@ -140,6 +142,24 @@ export default class ShipyardPage extends Page {
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
* @param {String} shipPredicate Sort predicate - property name
@@ -180,10 +200,11 @@ export default class ShipyardPage extends Page {
style={{ height: '1.5em' }}
className={cn({
highlighted: noTouch && this.state.shipId === s.id,
comparehighlight: this.state.compare[s.id],
})}
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
onClick={() => this._toggleCompare(s.id)}
>
<td className="ri">{s.manufacturer}</td>
<td className="ri">{fInt(s.retailCost)}</td>
<td className="ri cap">{translate(SizeMap[s.class])}</td>
<td className="ri">{fInt(s.crew)}</td>
@@ -234,7 +255,7 @@ export default class ShipyardPage extends Page {
let hide = this.context.tooltip.bind(null, null);
let fInt = formats.int;
let fRound = formats.round;
let { shipSummaries, shipPredicate, shipPredicateIndex } = this.state;
let { shipSummaries, shipPredicate, shipPredicateIndex, compare, groupCompared } = this.state;
let sortShips = (predicate, index) =>
this._sortShips.bind(this, predicate, index);
@@ -267,6 +288,15 @@ export default class ShipyardPage extends Page {
valB = val;
}
if (groupCompared) {
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) {
return 1;
@@ -298,8 +328,10 @@ export default class ShipyardPage extends Page {
style={{ height: '1.5em' }}
className={cn({
highlighted: noTouch && this.state.shipId === s.id,
comparehighlight: this.state.compare[s.id],
})}
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
onClick={() => this._toggleCompare(s.id)}
>
<td className="le">
<Link href={'/outfit/' + s.id}>{s.name} {s.beta === true ? '(Beta)' : null}</Link>
@@ -310,18 +342,10 @@ export default class ShipyardPage extends Page {
}
return (
<div className="page" style={{ fontSize: sizeRatio + 'em' }}>
<div
style={{
whiteSpace: 'nowrap',
margin: '0 auto',
fontSize: '0.8em',
position: 'relative',
display: 'inline-block',
maxWidth: '100%'
}}
>
<table style={{ width: '12em', position: 'absolute', zIndex: 1 }}>
<div className="page" style={{fontSize: sizeRatio + 'em'}}>
<div className="content-wrapper">
<div className="shipyard-table-wrapper">
<table style={{width: '12em', position: 'absolute', zIndex: 1}} className="shipyard-table">
<thead>
<tr>
<th className="le rgt">&nbsp;</th>
@@ -339,17 +363,10 @@ export default class ShipyardPage extends Page {
{shipRows}
</tbody>
</table>
<div style={{ overflowX: 'scroll', maxWidth: '100%' }}>
<table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }}>
<div style={{ overflowX: 'auto', maxWidth: '100%' }}>
<table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }} className="shipyard-table">
<thead>
<tr className="main">
<th
rowSpan={3}
className="sortable"
onClick={sortShips('manufacturer')}
>
{translate('manufacturer')}
</th>
<th>&nbsp;</th>
<th
rowSpan={3}
@@ -527,7 +544,7 @@ export default class ShipyardPage extends Page {
</th>
<th
className="sortable"
onMouseEnter={termtip.bind(null, 'power distriubtor')}
onMouseEnter={termtip.bind(null, 'power distributor')}
onMouseLeave={hide}
onClick={sortShips('standard', 4)}
>
@@ -597,6 +614,10 @@ export default class ShipyardPage extends Page {
</table>
</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>
);
}

View File

@@ -14,8 +14,9 @@ export function jumpRange(mass, fsd, fuel, ship) {
const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
let jumpAddition = 0;
if (ship) {
mass += ship.reserveFuelCapacity || 0;
for (const module of ship.internal) {
if (module && module.m && module.m.grp === 'gfsb') {
if (module && module.m && module.m.grp === 'gfsb' && ship.getSlotStatus(module) == 3) {
jumpAddition += module.m.getJumpBoost();
}
}
@@ -340,67 +341,34 @@ export function shieldMetrics(ship, sys) {
const maxSysResistance = this.sysResistance(4);
let shield = {};
const dimReturnLine = (res) => 1 - (1 - res) * 0.7;
const shieldGeneratorSlot = ship.findInternalByGroup('sg');
if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) {
const shieldGenerator = shieldGeneratorSlot.m;
let res = {
kin: shieldGenerator.kinres,
therm: shieldGenerator.thermres,
expl: shieldGenerator.explres
};
// Boosters
let boost = 1;
let boosterExplDmg = 1;
let boosterKinDmg = 1;
let boosterThermDmg = 1;
// const explDim = dimReturnLine(shieldGenerator.explres);
// const thermDim = dimReturnLine(shieldGenerator.thermres);
// const kinDim = dimReturnLine(shieldGenerator.kinres);
for (let slot of ship.hardpoints) {
if (slot.enabled && slot.m && slot.m.grp == 'sb') {
boost += slot.m.getShieldBoost();
res.expl += slot.m.getExplosiveResistance();
res.kin += slot.m.getKineticResistance();
res.therm += slot.m.getThermalResistance();
boosterExplDmg = boosterExplDmg * (1 - slot.m.getExplosiveResistance());
boosterKinDmg = boosterKinDmg * (1 - slot.m.getKineticResistance());
boosterThermDmg = boosterThermDmg * (1 - slot.m.getThermalResistance());
}
if (slot.m && slot.m.grp == 'gsrp') {
}
}
// Calculate diminishing returns for boosters
// Diminishing returns not currently in-game
// boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5);
// Remove base shield generator strength
boost -= 1;
// if (res.expl > explDim) {
// const overage = (res.expl - explDim) * 0.5;
// res.expl = explDim + overage;
// boosterExplDmg = explDim + overage;
// }
//
// if (res.therm > thermDim) {
// const overage = (res.therm - thermDim) * 0.5;
// res.therm = thermDim + overage;
// boosterThermDmg = thermDim + overage;
// }
//
// if (res.kin > kinDim) {
// const overage = (res.kin - kinDim) * 0.5;
// res.kin = kinDim + overage;
// boosterKinDmg = kinDim + overage;
// }
let shieldAddition = 0;
if (ship) {
for (const module of ship.internal) {
if (module && module.m && module.m.grp === 'gsrp') {
if (module && module.m && module.m.grp === 'gsrp' && module.enabled) {
shieldAddition += module.m.getShieldAddition();
}
}
@@ -415,7 +383,8 @@ export function shieldMetrics(ship, sys) {
// 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
let capacitorDrain = (shieldGenerator.getBrokenRegenerationRate() * 0.6) - sysRechargeRate;
let capacitorDrain = (shieldGenerator.getBrokenRegenerationRate() * shieldGenerator.getDistDraw()) - sysRechargeRate;
let capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
let recover = 16;
@@ -431,7 +400,7 @@ export function shieldMetrics(ship, sys) {
recover = Math.Infinity;
} else {
// Recover remaining shields at the rate of the power distributor's recharge
recover += remainingShieldToRecover / (sysRechargeRate / 0.6);
recover += remainingShieldToRecover / (sysRechargeRate / shieldGenerator.getDistDraw());
}
}
@@ -440,7 +409,7 @@ export function shieldMetrics(ship, sys) {
// 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
capacitorDrain = (shieldGenerator.getRegenerationRate() * 0.6) - sysRechargeRate;
capacitorDrain = (shieldGenerator.getRegenerationRate() * shieldGenerator.getDistDraw()) - sysRechargeRate;
capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
let recharge = 0;
@@ -456,7 +425,7 @@ export function shieldMetrics(ship, sys) {
recharge = Math.Inf;
} else {
// Recharge remaining shields at the rate of the power distributor's recharge
recharge += remainingShieldToRecharge / (sysRechargeRate / 0.6);
recharge += remainingShieldToRecharge / (sysRechargeRate / shieldGenerator.getDistDraw());
}
}
@@ -465,6 +434,7 @@ export function shieldMetrics(ship, sys) {
boosters: boostersStrength,
addition: shieldAddition,
cells: ship.shieldCells,
summary: generatorStrength + boostersStrength + shieldAddition,
total: generatorStrength + boostersStrength + ship.shieldCells + shieldAddition,
recover,
recharge,
@@ -497,7 +467,7 @@ export function shieldMetrics(ship, sys) {
*/
let sgExplosiveDmg = 1 - shieldGenerator.getExplosiveResistance();
let sgSbExplosiveDmg = diminishDamageMult(sgExplosiveDmg * 0.7, (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg);
let sgSbExplosiveDmg = diminishingReturnsShields(sgExplosiveDmg, sgExplosiveDmg * boosterExplDmg);
/** @type {ShieldDamageMults} */
shield.explosive = {
generator: sgExplosiveDmg,
@@ -509,7 +479,7 @@ export function shieldMetrics(ship, sys) {
};
let sgKineticDmg = 1 - shieldGenerator.getKineticResistance();
let sgSbKineticDmg = diminishDamageMult(sgKineticDmg * 0.7, (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg);
let sgSbKineticDmg = diminishingReturnsShields(sgKineticDmg, sgKineticDmg * boosterKinDmg);
/** @type {ShieldDamageMults} */
shield.kinetic = {
generator: sgKineticDmg,
@@ -521,7 +491,7 @@ export function shieldMetrics(ship, sys) {
};
let sgThermalDmg = 1 - shieldGenerator.getThermalResistance();
let sgSbThermalDmg = diminishDamageMult(sgThermalDmg * 0.7, (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg);
let sgSbThermalDmg = diminishingReturnsShields(sgThermalDmg , sgThermalDmg * boosterThermDmg);
/** @type {ShieldDamageMults} */
shield.thermal = {
generator: sgThermalDmg,
@@ -561,58 +531,28 @@ export function armourMetrics(ship) {
let moduleArmour = 0;
let moduleProtection = 1;
const bulkheads = ship.bulkheads.m;
let hullExplDmg = 1;
let hullKinDmg = 1;
let hullThermDmg = 1;
let hullCausDmg = 1;
// const dimReturnLine = (res) => 1 - (1 - res) * 0.7;
// let res = {
// kin: 0,
// therm: 0,
// expl: 0
// };
let hullExplDmgs = [];
let hullKinDmgs = [];
let hullThermDmgs = [];
let hullCausDmgs = [];
// Armour from HRPs and module armour from MRPs
for (let slot of ship.internal) {
if (slot.m && (slot.m.grp === 'hr' || slot.m.grp === 'ghrp' || slot.m.grp == 'mahr')) {
if (slot.m && slot.enabled && (slot.m.grp === 'hr' || slot.m.grp === 'ghrp' || slot.m.grp == 'mahr')) {
armourReinforcement += slot.m.getHullReinforcement();
// Hull boost for HRPs is applied against the ship's base armour
armourReinforcement += ship.baseArmour * slot.m.getModValue('hullboost') / 10000;
// res.expl += slot.m.getExplosiveResistance();
// res.kin += slot.m.getKineticResistance();
// res.therm += slot.m.getThermalResistance();
hullExplDmg = hullExplDmg * (1 - slot.m.getExplosiveResistance());
hullKinDmg = hullKinDmg * (1 - slot.m.getKineticResistance());
hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance());
hullCausDmg = hullCausDmg * (1 - slot.m.getCausticResistance());
hullExplDmgs.push(1 - slot.m.getExplosiveResistance());
hullKinDmgs.push(1 - slot.m.getKineticResistance());
hullThermDmgs.push(1 - slot.m.getThermalResistance());
hullCausDmgs.push(1 - slot.m.getCausticResistance());
}
if (slot.m && (slot.m.grp == 'mrp' || slot.m.grp == 'gmrp')) {
if (slot.m && slot.enabled && (slot.m.grp == 'mrp' || slot.m.grp == 'gmrp')) {
moduleArmour += slot.m.getIntegrity();
moduleProtection = moduleProtection * (1 - slot.m.getProtection());
}
}
moduleProtection = 1 - moduleProtection;
// const explDim = dimReturnLine(bulkheads.explres);
// const thermDim = dimReturnLine(bulkheads.thermres);
// const kinDim = dimReturnLine(bulkheads.kinres);
// if (res.expl > explDim) {
// const overage = (res.expl - explDim) * 0.5;
// res.expl = explDim + overage;
// hullExplDmg = explDim + overage;
// }
//
// if (res.therm > thermDim) {
// const overage = (res.therm - thermDim) * 0.5;
// res.therm = thermDim + overage;
// hullThermDmg = thermDim + overage;
// }
//
// if (res.kin > kinDim) {
// const overage = (res.kin - kinDim) * 0.5;
// res.kin = kinDim + overage;
// hullKinDmg = kinDim + overage;
// }
const armour = {
bulkheads: armourBulkheads,
reinforcement: armourReinforcement,
@@ -629,8 +569,8 @@ export function armourMetrics(ship) {
total: 1
};
let armourExplDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getExplosiveResistance());
let armourReinforcedExplDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getExplosiveResistance()) * hullExplDmg);
let armourExplDmg = 1 - ship.bulkheads.m.getExplosiveResistance();
let armourReinforcedExplDmg = diminishingReturnsArmour(armourExplDmg, ...hullExplDmgs);
armour.explosive = {
bulkheads: armourExplDmg,
reinforcement: armourReinforcedExplDmg / armourExplDmg,
@@ -638,8 +578,8 @@ export function armourMetrics(ship) {
res: 1 - armourReinforcedExplDmg
};
let armourKinDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getKineticResistance());
let armourReinforcedKinDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getKineticResistance()) * hullKinDmg);
let armourKinDmg = 1 - ship.bulkheads.m.getKineticResistance();
let armourReinforcedKinDmg = diminishingReturnsArmour(armourKinDmg, ...hullKinDmgs);
armour.kinetic = {
bulkheads: armourKinDmg,
reinforcement: armourReinforcedKinDmg / armourKinDmg,
@@ -647,8 +587,8 @@ export function armourMetrics(ship) {
res: 1 - armourReinforcedKinDmg
};
let armourThermDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getThermalResistance());
let armourReinforcedThermDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg);
let armourThermDmg = 1 - ship.bulkheads.m.getThermalResistance();
let armourReinforcedThermDmg = diminishingReturnsArmour(armourThermDmg, ...hullThermDmgs);
armour.thermal = {
bulkheads: armourThermDmg,
reinforcement: armourReinforcedThermDmg / armourThermDmg,
@@ -656,8 +596,8 @@ export function armourMetrics(ship) {
res: 1 - armourReinforcedThermDmg
};
let armourCausDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getCausticResistance());
let armourReinforcedCausDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getCausticResistance()) * hullCausDmg);
let armourCausDmg = 1 - ship.bulkheads.m.getCausticResistance();
let armourReinforcedCausDmg = diminishingReturnsArmour(armourCausDmg, ...hullCausDmgs);
armour.caustic = {
bulkheads: armourCausDmg,
reinforcement: armourReinforcedCausDmg / armourCausDmg,
@@ -903,12 +843,14 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
shields: {
range: 1,
sys: opponentHasShields ? opponentShields.absolute.sys : 1,
resistance: 1
resistance: 1,
dpe: 1
},
armour: {
range: 1,
hardness: 1,
resistance: 1
resistance: 1,
dpe: 1
}
}
};
@@ -968,11 +910,19 @@ 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.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.armour.resistance *= armourResistance;
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.shields.dpe = weapon.damage.shields.total / m.getEps() / m.getSustainedFactor();
weapon.effectiveness.armour.dpe = weapon.damage.armour.total / m.getEps() / m.getSustainedFactor();
return weapon;
}
@@ -1014,7 +964,10 @@ export function timeToDrainWep(ship, wep) {
*/
export function timeToDeplete(amount, dps, eps, capacity, recharge) {
const drainPerSecond = eps - recharge;
if (drainPerSecond <= 0) {
// If there is nothing to remove, we're don instantly
if (!amount) {
return 0;
} if (drainPerSecond <= 0) {
// Simple result
return amount / dps;
} else {
@@ -1033,15 +986,50 @@ export function timeToDeplete(amount, dps, eps, capacity, recharge) {
}
/**
* Applies diminishing returns to resistances.
* @param {number} diminishFrom The base resistance up to which no diminishing returns are applied.
* @param {number} damageMult Resistance as damage multiplier
* @returns {number} Actual damage multiplier
* 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 diminishDamageMult(diminishFrom, damageMult) {
if (damageMult > diminishFrom) {
return damageMult;
export function diminishingReturnsShields(shieldMult, combinedMult) {
let max = shieldMult * 0.7;
if (combinedMult < max) {
return mapIntoDiminishingRange(max / 2, max, combinedMult);
} else {
return (diminishFrom / 2) + 0.5 * damageMult;
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

@@ -57,6 +57,9 @@ export const ModuleGroupToName = {
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
bl: 'Beam Laser',
@@ -206,7 +209,7 @@ export const ShipFacets = [
i: 9
},
{ // 10
title: 'fastest range',
title: 'farthest range',
props: ['unladenFastestRange', 'ladenFastestRange'],
lbls: ['unladen', 'laden'],
unit: 'LY',

View File

@@ -59,14 +59,7 @@ export default class Module {
} else if (modification.method === 'overwrite') {
result = modifierActions[name];
} else {
// rate of fire is special, as it's really burst interval. Handle that here
let mod = null;
if (name === 'rof') {
mod = 1 / (1 + modifierActions[name]) - 1;
} else {
mod = modifierActions[name];
}
result = (((1 + result / multiplier) * (1 + mod)) - 1) * multiplier;
result = (((1 + result / multiplier) * (1 + modifierActions[name])) - 1) * multiplier;
}
}
}
@@ -77,7 +70,7 @@ export default class Module {
/**
* Set a value for a given modification ID
* @param {Number} name The name of the modification
* @param {String} name The name of the modification
* @param {object} value The value of the modification. If it is a numeric value then it should be an integer scaled so that -2.34% == -234
* @param {Boolean} valueiswithspecial true if the value includes the special effect (when coming from a UI component)
*/
@@ -88,7 +81,13 @@ export default class Module {
if (!this.origVals) {
this.origVals = {};
}
if (valueiswithspecial && this.blueprint && this.blueprint.special) {
if (!valueiswithspecial) {
// Resistance modifiers scale with the base value.
if (name === 'kinres' || name === 'thermres' || name === 'causres' || name === 'explres') {
let baseValue = this.get(name, false);
value = (1 - baseValue) * value;
}
} else if (valueiswithspecial && this.blueprint && this.blueprint.special) {
// This module has a special effect, see if we need to alter the stored value
const modifierActions = Modifications.modifierActions[this.blueprint.special.edname];
if (modifierActions && modifierActions[name]) {
@@ -105,14 +104,7 @@ export default class Module {
} else if (modification.method === 'overwrite') {
value = null;
} else {
// rate of fire is special, as it's really burst interval. Handle that here
let mod = null;
if (name === 'rof') {
mod = 1 / (1 + modifierActions[name]) - 1;
} else {
mod = modifierActions[name];
}
value = ((value / 10000 + 1) / (1 + mod) - 1) * 10000;
value = ((value / 10000 + 1) / (1 + modifierActions[name]) - 1) * 10000;
}
}
}
@@ -127,10 +119,17 @@ export default class Module {
/**
* Helper to obtain a module's value.
* @param {String} name The name of the modifier to obtain
* @param {Number} modified Whether to return the raw or modified value
* @param {Boolean} modified Whether to return the raw or modified value
* @return {Number} The value queried
*/
get(name, modified = true) {
if (name == 'rof' && isNaN(this[name])) {
let fireint = this['fireint'];
if (!isNaN(fireint)) {
this['rof'] = 1 / fireint;
}
}
let val;
if (modified) {
val = this._getModifiedValue(name);
@@ -166,14 +165,20 @@ export default class Module {
modValue = value - baseValue;
} else if (name === 'shieldboost' || name === 'hullboost') {
modValue = (1 + value) / (1 + baseValue) - 1;
} else if (name === 'rof') {
let burst = this.get('burst', true) || 1;
let burstInt = 1 / (this.get('burstrof', true) / 1);
let interval = burst / value;
let newFireint = (interval - (burst - 1) * burstInt);
modValue = newFireint / this['fireint'] - 1;
} else { // multiplicative
modValue = baseValue == 0 ? 0 : value / baseValue - 1;
}
if (modification.type === 'percentage') {
modValue = modValue * 10000;
} else if (modification.type === 'numeric' && name !== 'burst' &&
name !== 'burstrof') {
} else if (modification.type === 'numeric') {
modValue = modValue * 100;
}
@@ -191,7 +196,14 @@ export default class Module {
*/
getPretty(name, modified = true, places = 2) {
const formattingOptions = STATS_FORMATTING[name];
let val = this.get(name, modified) || 0;
let val;
if (formattingOptions && formattingOptions.synthetic) {
val = (this[formattingOptions.synthetic]).call(this, modified);
} else {
val = this.get(name, modified);
}
val = val || 0;
if (formattingOptions && formattingOptions.format.startsWith('pct')) {
return 100 * val;
}
@@ -250,12 +262,17 @@ export default class Module {
} else if (name === 'shieldboost' || name === 'hullboost') {
result = (1 + result) * (1 + modValue) - 1;
} else {
// Rate of fire modifiers are special as they actually are modifiers
// for fire interval. Translate them accordingly here:
if (name == 'rof') {
modValue = 1 / (1 + modValue) - 1;
}
result = result * (1 + modValue);
}
} else if (name === 'burstrof') {
} else if (name === 'burstrof' || name === 'burst') {
// Burst and burst rate of fire are special, as it can not exist but
// have a modification
result = modValue / 100;
result = modValue;
}
}
}
@@ -277,11 +294,7 @@ export default class Module {
formatModifiedValue(name, language, unit, val) {
const formattingOptions = STATS_FORMATTING[name];
if (val === undefined) {
if (formattingOptions && formattingOptions.synthetic) {
val = (this[formattingOptions.synthetic]).call(this, true);
} else {
val = this._getModifiedValue(name);
}
val = this.getPretty(name, true);
}
val = val || 0;
@@ -351,14 +364,18 @@ export default class Module {
if (formattingOptions && formattingOptions.change) {
let changeFormatting = formattingOptions.change;
let baseVal = this[name];
let baseVal = this[name] || 0;
let absVal = this._getModifiedValue(name);
if (changeFormatting === 'additive') {
val = absVal - baseVal;
} else if (changeFormatting === 'multiplicative') {
val = absVal / baseVal - 1;
}
val *= 10000;
if (Modifications.modifications[name].method === 'overwrite') {
val *= 100;
} else {
val *= 10000;
}
}
return val;
}
@@ -422,6 +439,15 @@ export default class Module {
return this.get('integrity', modified);
}
/**
* Get the info of this module
* @param {Boolean} [modified=false] Whether to take modifications into account
* @return {String} the info of this module
*/
getInfo(modified = false) {
return (modified && this.getModValue('info')) || this.info;
}
/**
* Get the mass of this module
* @param {Boolean} [modified=true] Whether to take modifications into account
@@ -572,20 +598,9 @@ export default class Module {
* @return {Number} the falloff of this module
*/
getFalloff(modified = true) {
if (!modified) {
const range = this.getRange(false);
const falloff = this.get('falloff', false);
return (falloff > range ? range : falloff);
}
// Falloff from range is mapped to range
if (this.mods && this.mods['fallofffromrange']) {
if (modified && this.mods && this.mods['fallofffromrange']) {
return this.getRange();
// Need to find out if we have a focused modification, in which case our
// falloff is scaled to range
} else if (this.blueprint && this.blueprint.name === 'Focused') {
const rangeMod = this.getModValue('range') / 10000;
return this.falloff * (1 + rangeMod);
// Standard falloff calculation
} else {
const range = this.getRange();
@@ -639,15 +654,6 @@ export default class Module {
return this.get('protection', modified);
}
/**
* Get the delay for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
* @return {Number} the delay of this module
*/
getDelay(modified = true) {
return this.get('delay', modified);
}
/**
* Get the duration for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
@@ -703,7 +709,7 @@ export default class Module {
let result = 0;
if (this['maxmass']) {
result = this['maxmass'];
if (result && modified) {
if (result && modified && !ModuleUtils.isShieldGenerator(this['grp'])) {
let mult = this.getModValue('optmass') / 10000;
if (mult) { result = result * (1 + mult); }
}
@@ -792,24 +798,6 @@ export default class Module {
return this.get('distdraw', modified);
}
/**
* Get the thermal load for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
* @return {Number} the thermal load of this module
*/
getThermalLoad(modified = true) {
return this.get('thermload', modified);
}
/**
* Get the rounds per shot for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
* @return {Number} the rounds per shot of this module
*/
getRoundsPerShot(modified = true) {
return this.get('roundspershot', modified);
}
/**
* Get the DPS for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
@@ -834,26 +822,40 @@ export default class Module {
}
/**
* Get the SDPS for this module
* Return the factor that gets applied when calculating certain "sustained"
* values, e.g. `SDPS = this.getSustainedFactor() * DPS`.
* @param {Boolean} [modified=true] Whether to take modifications into account
* @return {Number} The SDPS of this module
*/
getSDps(modified = true) {
let dps = this.getDps(modified);
if (this.getClip(modified)) {
let clipSize = this.getClip(modified);
getSustainedFactor(modified = true) {
let clipSize = this.getClip(modified);
if (clipSize) {
// If auto-loader is applied, effective clip size will be nearly doubled
// as you get one reload for every two shots fired.
if (this.blueprint && this.blueprint.special && this.blueprint.special.edname === 'special_auto_loader' && modified) {
clipSize += clipSize - 1;
}
let timeToDeplete = clipSize / this.getRoF(modified);
return dps * timeToDeplete / (timeToDeplete + this.getReload(modified));
let burstSize = this.get('burst', modified) || 1;
let rof = this.getRoF(modified);
// rof averages burstfire + pause until next interval but for sustained
// rof we need to take another burst without pause into account
let burstOverhead = (burstSize - 1) / (this.get('burstrof', modified) || 1);
let srof = clipSize / ((clipSize - burstSize) / rof + burstOverhead + this.getReload(modified));
return srof / rof;
} else {
return dps;
return 1;
}
}
/**
* Get the SDPS for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
* @return {Number} The SDPS of this module
*/
getSDps(modified = true) {
return this.getDps(modified) * this.getSustainedFactor(modified);
}
/**
* Get the EPS for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
@@ -875,7 +877,7 @@ export default class Module {
*/
getHps(modified = true) {
// HPS is a synthetic value
let heat = this.getThermalLoad(modified);
let heat = this.get('thermload', modified);
// We don't use rpshot here as dist draw is per combined shot
let rof = this.getRoF(modified) || 1;
@@ -912,24 +914,6 @@ export default class Module {
return this.get('reload', modified);
}
/**
* Get the burst size for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
* @return {Number} the burst size of this module
*/
getBurst(modified = true) {
return this.get('burst', modified);
}
/**
* Get the burst rate of fire for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
* @return {Number} the burst rate of fire of this module
*/
getBurstRoF(modified = true) {
return this.get('burstrof', modified);
}
/**
* Get the rate of fire for this module.
* The rate of fire is a combination value, and needs to take in to account
@@ -940,8 +924,8 @@ export default class Module {
* @return {Number} the rate of fire for this module
*/
getRoF(modified = true) {
const burst = this.getBurst(modified) || 1;
const burstRoF = this.getBurstRoF(modified) || 1;
const burst = this.get('burst', modified) || 1;
const burstRoF = this.get('burstrof', modified) || 1;
const intRoF = this.get('rof', modified);
return burst / (((burst - 1) / burstRoF) + 1 / intRoF);

View File

@@ -13,6 +13,19 @@ function filter(arr, maxClass, minClass, mass) {
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
*/
@@ -41,6 +54,7 @@ export default class ModuleSet {
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] = 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[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)

View File

@@ -10,7 +10,7 @@ import { Ships, Modifications } from 'coriolis-data/dist';
import { chain } from 'lodash';
const zlib = require('zlib');
const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh', 'gfsb'];
const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh', 'gfsb', 'dc', 'ews'];
// Constants for modifications struct
const SLOT_ID_DONE = -1;
@@ -492,25 +492,18 @@ export default class Ship {
* @param {Object} m The module to change
* @param {Object} name The name of the modification to change
* @param {Number} value The new value of the modification. The value of the modification is scaled to provide two decimal places of precision in an integer. For example 1.23% is stored as 123
* @param {bool} sentfromui True if this update was sent from the UI
* @param {bool} isAbsolute True if value is an absolute value and not a
* modification value
* @param {boolean} isAbsolute True if value is an absolute value and not a modification value
*/
setModification(m, name, value, sentfromui, isAbsolute) {
setModification(m, name, value, isAbsolute = false) {
if (isNaN(value)) {
// Value passed is invalid; reset it to 0
value = 0;
}
if (isAbsolute) {
m.setPretty(name, value, sentfromui);
m.setPretty(name, value, isAbsolute);
} else {
// Resistance modifiers scale with the base value
if (name == 'kinres' || name == 'thermres' || name == 'causres' || name == 'explres') {
let baseValue = m.get(name, false);
value = (1 - baseValue) * value;
}
m.setModValue(name, value, sentfromui);
m.setModValue(name, value, false);
}
// Handle special cases
@@ -543,7 +536,7 @@ export default class Ship {
this.recalculateArmour();
} else if (name === 'shieldreinforcement') {
this.recalculateShieldCells();
} else if (name === 'burst' || name == 'burstrof' || name === 'clip' || name === 'damage' || name === 'distdraw' || name === 'jitter' || name === 'piercing' || name === 'range' || name === 'reload' || name === 'rof' || name === 'thermload') {
} else if (name === 'burst' || name === 'burstrof' || name === 'clip' || name === 'damage' || name === 'distdraw' || name === 'jitter' || name === 'piercing' || name === 'range' || name === 'reload' || name === 'rof' || name === 'thermload') {
this.recalculateDps();
this.recalculateHps();
this.recalculateEps();

View File

@@ -26,8 +26,8 @@ export const STATS_FORMATTING = {
'ammo': { 'format': 'int', },
'boot': { 'format': 'int', 'unit': 'secs' },
'brokenregen': { 'format': 'round1', 'unit': 'ps' },
'burst': { 'format': 'int' },
'burstrof': { 'format': 'round1', 'unit': 'ps' },
'burst': { 'format': 'int', 'change': 'additive' },
'burstrof': { 'format': 'round1', 'unit': 'ps', 'change': 'additive' },
'causres': { 'format': 'pct' },
'clip': { 'format': 'int' },
'damage': { 'format': 'round' },
@@ -61,7 +61,7 @@ export const STATS_FORMATTING = {
'ranget': { 'format': 'f1', 'unit': 's' },
'regen': { 'format': 'round1', 'unit': 'ps' },
'reload': { 'format': 'int', 'unit': 's' },
'rof': { 'format': 'round1', 'unit': 'ps' },
'rof': { 'format': 'round1', 'unit': 'ps', 'synthetic': 'getRoF', 'higherbetter': true },
'angle': { 'format': 'round1', 'unit': 'ang' },
'scanrate': { 'format': 'int' },
'scantime': { 'format': 'round1', 'unit': 's' },

View File

@@ -15,7 +15,6 @@ const LS_KEY_SIZE_RATIO = 'sizeRatio';
const LS_KEY_TOOLTIPS = 'tooltips';
const LS_KEY_MODULE_RESISTANCES = 'moduleResistances';
const LS_KEY_ROLLS = 'matsPerGrade';
const LS_KEY_ORBIS = 'orbis';
let LS;
@@ -95,7 +94,6 @@ export class Persist extends EventEmitter {
let buildJson = _get(LS_KEY_BUILDS);
let comparisonJson = _get(LS_KEY_COMPARISONS);
this.orbisCreds = _get(LS_KEY_ORBIS) || { email: '', password: '' };
this.onStorageChange = this.onStorageChange.bind(this);
this.langCode = _getString(LS_KEY_LANG) || 'en';
this.insurance = insurance && Insurance[insurance.toLowerCase()] !== undefined ? insurance : 'standard';
@@ -169,10 +167,6 @@ export class Persist extends EventEmitter {
this.matsPerGrade = JSON.parse(newValue);
this.emit('matsPerGrade', this.matsPerGrade);
break;
case LS_KEY_ORBIS:
this.orbisCreds = JSON.parse(newValue);
this.emit('orbis', this.orbisCreds);
break;
}
} catch (e) {
// On JSON.Parse Error - don't sync or do anything
@@ -198,24 +192,6 @@ export class Persist extends EventEmitter {
this.emit('language', langCode);
}
/**
* Get the current orbis.zone credentials
* @return {String} language code
*/
getOrbisCreds() {
return this.orbisCreds;
};
/**
* Update and save the orbis.zone credentials
* @param {Object} creds object with username and password properties.
*/
setOrbisCreds(creds) {
this.langCode = creds;
_put(LS_KEY_ORBIS, creds);
this.emit('orbis', creds);
}
/**
* Show tooltips setting
* @param {boolean} show Optional - update setting

View File

@@ -1,417 +1,435 @@
import React from 'react';
import { Modifications } from 'coriolis-data/dist';
/**
* Generate a tooltip with details of a blueprint's specials
* @param {Object} translate The translate object
* @param {Object} blueprint The blueprint at the required grade
* @param {string} grp The group of the module
* @param {Object} m The module to compare with
* @param {string} specialName The name of the special
* @returns {Object} The react components
*/
export function specialToolTip(translate, blueprint, grp, m, specialName) {
const effects = [];
if (!blueprint || !blueprint.features) {
return undefined;
}
if (m) {
// We also add in any benefits from specials that aren't covered above
if (m.blueprint) {
for (const feature in Modifications.modifierActions[specialName]) {
// if (!blueprint.features[feature] && !m.mods.feature) {
const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature) - m.getModValue(feature, true);
if (featureDef.type === 'percentage') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature + '_specialTT'}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'}
style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
}
return (
<div>
<table width='100%'>
<tbody>
{effects}
</tbody>
</table>
</div>
);
}
/**
* Generate a tooltip with details of a blueprint's effects
* @param {Object} translate The translate object
* @param {Object} blueprint The blueprint at the required grade
* @param {Array} engineers The engineers supplying this blueprint
* @param {string} grp The group of the module
* @param {Object} m The module to compare with
* @returns {Object} The react components
*/
export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
const effects = [];
if (!blueprint || !blueprint.features) {
return undefined;
}
for (const feature in blueprint.features) {
const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]);
const featureDef = Modifications.modifications[feature];
if (!featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let lowerBound = blueprint.features[feature][0];
let upperBound = blueprint.features[feature][1];
if (featureDef.type === 'percentage') {
lowerBound = Math.round(lowerBound * 1000) / 10;
upperBound = Math.round(upperBound * 1000) / 10;
}
const lowerIsBeneficial = isValueBeneficial(feature, lowerBound);
const upperIsBeneficial = isValueBeneficial(feature, upperBound);
if (m) {
// We have a module - add in the current value
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
</tr>
);
} else {
// We do not have a module, no value
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
</tr>
);
}
}
}
if (m) {
// Because we have a module add in any benefits that aren't part of the primary blueprint
for (const feature in m.mods) {
if (!blueprint.features[feature]) {
const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
// We also add in any benefits from specials that aren't covered above
if (m.blueprint && m.blueprint.special) {
for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) {
if (!blueprint.features[feature] && !m.mods.feature) {
const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
}
}
let components;
if (!m) {
components = [];
for (const component in blueprint.components) {
components.push(
<tr key={component}>
<td style={{ textAlign: 'left' }}>{translate(component)}</td>
<td style={{ textAlign: 'right' }}>{blueprint.components[component]}</td>
</tr>
);
}
}
let engineersList;
if (engineers) {
engineersList = [];
for (const engineer of engineers) {
engineersList.push(
<tr key={engineer}>
<td style={{ textAlign: 'left' }}>{engineer}</td>
</tr>
);
}
}
return (
<div>
<table width='100%'>
<thead>
<tr>
<td>{translate('feature')}</td>
<td>{translate('worst')}</td>
{m ? <td>{translate('current')}</td> : null }
<td>{translate('best')}</td>
</tr>
</thead>
<tbody>
{effects}
</tbody>
</table>
{ components ? <table width='100%'>
<thead>
<tr>
<td>{translate('component')}</td>
<td>{translate('amount')}</td>
</tr>
</thead>
<tbody>
{components}
</tbody>
</table> : null }
{ engineersList ? <table width='100%'>
<thead>
<tr>
<td>{translate('engineers')}</td>
</tr>
</thead>
<tbody>
{engineersList}
</tbody>
</table> : null }
</div>
);
}
/**
* Is this blueprint feature beneficial?
* @param {string} feature The name of the feature
* @param {array} values The value of the feature
* @returns {boolean} True if this feature is beneficial
*/
export function isBeneficial(feature, values) {
const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0));
if (Modifications.modifications[feature].higherbetter) {
return !fact;
} else {
return fact;
}
}
/**
* Is this feature value beneficial?
* @param {string} feature The name of the feature
* @param {number} value The value of the feature
* @returns {boolean} True if this value is beneficial
*/
export function isValueBeneficial(feature, value) {
if (Modifications.modifications[feature].higherbetter) {
return value > 0;
} else {
return value < 0;
}
}
/**
* Get a blueprint with a given name and an optional module
* @param {string} name The name of the blueprint
* @param {Object} module The module for which to obtain this blueprint
* @returns {Object} The matching blueprint
*/
export function getBlueprint(name, module) {
// Start with a copy of the blueprint
const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0);
const found = Modifications.blueprints[findMod(name)];
if (!found || !found.fdname) {
return {};
}
const blueprint = JSON.parse(JSON.stringify(found));
return blueprint;
}
/**
* Provide 'percent' primary modifications
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
* @param {Number} percent The percent to set values to of full.
*/
export function setPercent(ship, m, percent) {
ship.clearModifications(m);
// Pick given value as multiplier
const mult = percent / 100;
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
let value;
if (Modifications.modifications[featureName].higherbetter) {
// Higher is better, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
} else {
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
}
} else {
// Higher is worse, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
} else {
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
}
}
_setValue(ship, m, featureName, value);
}
}
/**
* Provide 'random' primary modifications
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
*/
export function setRandom(ship, m) {
// Pick a single value for our randomness
setPercent(ship, m, Math.random() * 100);
}
/**
* Set a modification feature value
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
* @param {string} featureName The feature being set
* @param {number} value The value being set for the feature
*/
function _setValue(ship, m, featureName, value) {
if (Modifications.modifications[featureName].type == 'percentage') {
ship.setModification(m, featureName, value * 10000);
} else if (Modifications.modifications[featureName].type == 'numeric') {
ship.setModification(m, featureName, value * 100);
} else {
ship.setModification(m, featureName, value);
}
}
/**
* Provide 'percent' primary query
* @param {Object} m The module for which to perform the query
* @returns {Number} percent The percentage indicator of current applied values.
*/
export function getPercent(m) {
let result = null;
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
if (features[featureName][0] === features[featureName][1]) {
continue;
}
let value = _getValue(m, featureName);
let mult;
if (featureName == 'shieldboost') {
mult = ((1 + value) * (1 + m.shieldboost)) - 1 - m.shieldboost;
} else if (Modifications.modifications[featureName].higherbetter) {
// Higher is better, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100);
} else {
mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100);
}
} else {
// Higher is worse, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100);
} else {
mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100);
}
}
if (result && result != mult) {
return null;
} else if (result != mult) {
result = mult;
}
}
return result;
}
/**
* Query a feature value
* @param {Object} m The module for which to perform the query
* @param {string} featureName The feature being queried
* @returns {number} The value of the modification as a %
*/
function _getValue(m, featureName) {
if (Modifications.modifications[featureName].type == 'percentage') {
return m.getModValue(featureName, true) / 10000;
} else if (Modifications.modifications[featureName].type == 'numeric') {
return m.getModValue(featureName, true) / 100;
} else {
return m.getModValue(featureName, true);
}
}
import React from 'react';
import { Modifications } from 'coriolis-data/dist';
import { STATS_FORMATTING } from '../shipyard/StatsFormatting';
/**
* Generate a tooltip with details of a blueprint's specials
* @param {Object} translate The translate object
* @param {Object} blueprint The blueprint at the required grade
* @param {string} grp The group of the module
* @param {Object} m The module to compare with
* @param {string} specialName The name of the special
* @returns {Object} The react components
*/
export function specialToolTip(translate, blueprint, grp, m, specialName) {
const effects = [];
if (!blueprint || !blueprint.features) {
return undefined;
}
if (m) {
// We also add in any benefits from specials that aren't covered above
if (m.blueprint) {
for (const feature in Modifications.modifierActions[specialName]) {
// if (!blueprint.features[feature] && !m.mods.feature) {
const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature) - m.getModValue(feature, true);
if (featureDef.type === 'percentage') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature + '_specialTT'}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'}
style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
}
return (
<div>
<table width='100%'>
<tbody>
{effects}
</tbody>
</table>
</div>
);
}
/**
* Generate a tooltip with details of a blueprint's effects
* @param {Object} translate The translate object
* @param {Object} blueprint The blueprint at the required grade
* @param {Array} engineers The engineers supplying this blueprint
* @param {string} grp The group of the module
* @param {Object} m The module to compare with
* @returns {Object} The react components
*/
export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
const effects = [];
if (!blueprint || !blueprint.features) {
return undefined;
}
for (const feature in blueprint.features) {
const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]);
const featureDef = Modifications.modifications[feature];
if (!featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let lowerBound = blueprint.features[feature][0];
let upperBound = blueprint.features[feature][1];
if (featureDef.type === 'percentage') {
lowerBound = Math.round(lowerBound * 1000) / 10;
upperBound = Math.round(upperBound * 1000) / 10;
}
const lowerIsBeneficial = isValueBeneficial(feature, lowerBound);
const upperIsBeneficial = isValueBeneficial(feature, upperBound);
if (m) {
// We have a module - add in the current value
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
</tr>
);
} else {
// We do not have a module, no value
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
</tr>
);
}
}
}
if (m) {
// Because we have a module add in any benefits that aren't part of the primary blueprint
for (const feature in m.mods) {
if (!blueprint.features[feature]) {
const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
// We also add in any benefits from specials that aren't covered above
if (m.blueprint && m.blueprint.special) {
for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) {
if (!blueprint.features[feature] && !m.mods.feature) {
const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
}
}
let components;
if (!m) {
components = [];
for (const component in blueprint.components) {
components.push(
<tr key={component}>
<td style={{ textAlign: 'left' }}>{translate(component)}</td>
<td style={{ textAlign: 'right' }}>{blueprint.components[component]}</td>
</tr>
);
}
}
let engineersList;
if (engineers) {
engineersList = [];
for (const engineer of engineers) {
engineersList.push(
<tr key={engineer}>
<td style={{ textAlign: 'left' }}>{engineer}</td>
</tr>
);
}
}
return (
<div>
<table width='100%'>
<thead>
<tr>
<td>{translate('feature')}</td>
<td>{translate('worst')}</td>
{m ? <td>{translate('current')}</td> : null }
<td>{translate('best')}</td>
</tr>
</thead>
<tbody>
{effects}
</tbody>
</table>
{ components ? <table width='100%'>
<thead>
<tr>
<td>{translate('component')}</td>
<td>{translate('amount')}</td>
</tr>
</thead>
<tbody>
{components}
</tbody>
</table> : null }
{ engineersList ? <table width='100%'>
<thead>
<tr>
<td>{translate('engineers')}</td>
</tr>
</thead>
<tbody>
{engineersList}
</tbody>
</table> : null }
</div>
);
}
/**
* Is this blueprint feature beneficial?
* @param {string} feature The name of the feature
* @param {array} values The value of the feature
* @returns {boolean} True if this feature is beneficial
*/
export function isBeneficial(feature, values) {
const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0));
if (Modifications.modifications[feature].higherbetter) {
return !fact;
} else {
return fact;
}
}
/**
* Is this feature value beneficial?
* @param {string} feature The name of the feature
* @param {number} value The value of the feature
* @returns {boolean} True if this value is beneficial
*/
export function isValueBeneficial(feature, value) {
if (Modifications.modifications[feature].higherbetter) {
return value > 0;
} else {
return value < 0;
}
}
/**
* Is the change as shown beneficial?
* @param {string} feature The name of the feature
* @param {number} value The value of the feature as percentage change
* @returns True if the value is beneficial
*/
export function isChangeValueBeneficial(feature, value) {
let changeHigherBetter = STATS_FORMATTING[feature].higherbetter;
if (changeHigherBetter === undefined) {
return isValueBeneficial(feature, value);
}
if (changeHigherBetter) {
return value > 0;
} else {
return value < 0;
}
}
/**
* Get a blueprint with a given name and an optional module
* @param {string} name The name of the blueprint
* @param {Object} module The module for which to obtain this blueprint
* @returns {Object} The matching blueprint
*/
export function getBlueprint(name, module) {
// Start with a copy of the blueprint
const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0);
const found = Modifications.blueprints[findMod(name)];
if (!found || !found.fdname) {
return {};
}
const blueprint = JSON.parse(JSON.stringify(found));
return blueprint;
}
/**
* Provide 'percent' primary modifications
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
* @param {Number} percent The percent to set values to of full.
*/
export function setPercent(ship, m, percent) {
ship.clearModifications(m);
// Pick given value as multiplier
const mult = percent / 100;
setQualityCB(m.blueprint, mult, (featureName, value) => ship.setModification(m, featureName, value));
}
/**
* Sets the blueprint quality and fires a callback for each property affected.
* @param {Object} blueprint The ship for which to perform the modifications
* @param {Number} quality The quality to apply - float number 0 to 1.
* @param {Function} cb The Callback to run for each property. Function (featureName, value)
*/
export function setQualityCB(blueprint, quality, cb) {
// Pick given value as multiplier
const features = blueprint.grades[blueprint.grade].features;
for (const featureName in features) {
let value;
if (Modifications.modifications[featureName].higherbetter) {
// Higher is better, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * quality);
} else {
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * quality);
}
} else {
// Higher is worse, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * quality);
} else {
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * quality);
}
}
if (Modifications.modifications[featureName].type == 'percentage') {
value = value * 10000;
} else if (Modifications.modifications[featureName].type == 'numeric') {
value = value * 100;
}
cb(featureName, value);
}
}
/**
* Provide 'random' primary modifications
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
*/
export function setRandom(ship, m) {
// Pick a single value for our randomness
setPercent(ship, m, Math.random() * 100);
}
/**
* Provide 'percent' primary query
* @param {Object} m The module for which to perform the query
* @returns {Number} percent The percentage indicator of current applied values.
*/
export function getPercent(m) {
let result = null;
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
if (features[featureName][0] === features[featureName][1]) {
continue;
}
let value = _getValue(m, featureName);
let mult;
if (Modifications.modifications[featureName].higherbetter) {
// Higher is better, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100);
} else {
mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100);
}
} else {
// Higher is worse, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100);
} else {
mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100);
}
}
if (result && result != mult) {
return null;
} else if (result != mult) {
result = mult;
}
}
return result;
}
/**
* Query a feature value
* @param {Object} m The module for which to perform the query
* @param {string} featureName The feature being queried
* @returns {number} The value of the modification as a %
*/
function _getValue(m, featureName) {
if (Modifications.modifications[featureName].type == 'percentage') {
return m.getModValue(featureName, true) / 10000;
} else if (Modifications.modifications[featureName].type == 'numeric') {
return m.getModValue(featureName, true) / 100;
} else {
return m.getModValue(featureName, true);
}
}

View File

@@ -34,6 +34,7 @@ export const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
'Krait_Light': 'krait_phantom',
'Orca': 'orca',
'Python': 'python',
'Python_nx': 'python_nx',
'SideWinder': 'sidewinder',
'Type6': 'type_6_transporter',
'Type7': 'type_7_transport',

View File

@@ -1,293 +1,395 @@
import Ship from '../shipyard/Ship';
import { HARDPOINT_NUM_TO_CLASS, shipModelFromJson } from './CompanionApiUtils';
import { Ships } from 'coriolis-data/dist';
import Module from '../shipyard/Module';
import { Modules } from 'coriolis-data/dist';
import { Modifications } from 'coriolis-data/dist';
import { getBlueprint } from './BlueprintFunctions';
/**
* Obtain a module given its FD Name
* @param {string} fdname the FD Name of the module
* @return {Module} the module
*/
function _moduleFromFdName(fdname) {
if (!fdname) return null;
fdname = fdname.toLowerCase();
// Check standard modules
for (const grp in Modules.standard) {
if (Modules.standard.hasOwnProperty(grp)) {
for (const i in Modules.standard[grp]) {
if (Modules.standard[grp][i].symbol && Modules.standard[grp][i].symbol.toLowerCase() === fdname) {
// Found it
return new Module({ template: Modules.standard[grp][i] });
}
}
}
}
// Check hardpoint modules
for (const grp in Modules.hardpoints) {
if (Modules.hardpoints.hasOwnProperty(grp)) {
for (const i in Modules.hardpoints[grp]) {
if (Modules.hardpoints[grp][i].symbol && Modules.hardpoints[grp][i].symbol.toLowerCase() === fdname) {
// Found it
return new Module({ template: Modules.hardpoints[grp][i] });
}
}
}
}
// Check internal modules
for (const grp in Modules.internal) {
if (Modules.internal.hasOwnProperty(grp)) {
for (const i in Modules.internal[grp]) {
if (Modules.internal[grp][i].symbol && Modules.internal[grp][i].symbol.toLowerCase() === fdname) {
// Found it
return new Module({ template: Modules.internal[grp][i] });
}
}
}
}
// Not found
return null;
}
/**
* Build a ship from the journal Loadout event JSON
* @param {object} json the Loadout event JSON
* @return {Ship} the built ship
*/
export function shipFromLoadoutJSON(json) {
// Start off building a basic ship
const shipModel = shipModelFromJson(json);
if (!shipModel) {
throw 'No such ship found: "' + json.Ship + '"';
}
const shipTemplate = Ships[shipModel];
let ship = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots);
ship.buildWith(null);
// Initial Ship building, don't do engineering yet.
let modsToAdd = [];
for (const module of json.Modules) {
switch (module.Slot.toLowerCase()) {
// Cargo Hatch.
case 'cargohatch':
ship.cargoHatch.enabled = module.On;
ship.cargoHatch.priority = module.Priority;
break;
// Add the bulkheads
case 'armour':
if (module.Item.toLowerCase().endsWith('_armour_grade1')) {
ship.useBulkhead(0, true);
} else if (module.Item.toLowerCase().endsWith('_armour_grade2')) {
ship.useBulkhead(1, true);
} else if (module.Item.toLowerCase().endsWith('_armour_grade3')) {
ship.useBulkhead(2, true);
} else if (module.Item.toLowerCase().endsWith('_armour_mirrored')) {
ship.useBulkhead(3, true);
} else if (module.Item.toLowerCase().endsWith('_armour_reactive')) {
ship.useBulkhead(4, true);
} else {
throw 'Unknown bulkheads "' + module.Item + '"';
}
ship.bulkheads.enabled = true;
if (module.Engineering) _addModifications(ship.bulkheads.m, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'powerplant':
const powerplant = _moduleFromFdName(module.Item);
ship.use(ship.standard[0], powerplant, true);
ship.standard[0].enabled = module.On;
ship.standard[0].priority = module.Priority;
if (module.Engineering) _addModifications(powerplant, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'mainengines':
const thrusters = _moduleFromFdName(module.Item);
ship.use(ship.standard[1], thrusters, true);
ship.standard[1].enabled = module.On;
ship.standard[1].priority = module.Priority;
if (module.Engineering) _addModifications(thrusters, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'frameshiftdrive':
const frameshiftdrive = _moduleFromFdName(module.Item);
ship.use(ship.standard[2], frameshiftdrive, true);
ship.standard[2].enabled = module.On;
ship.standard[2].priority = module.Priority;
if (module.Engineering) _addModifications(frameshiftdrive, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'lifesupport':
const lifesupport = _moduleFromFdName(module.Item);
ship.use(ship.standard[3], lifesupport, true);
ship.standard[3].enabled = module.On === true;
ship.standard[3].priority = module.Priority;
if (module.Engineering) _addModifications(lifesupport, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'powerdistributor':
const powerdistributor = _moduleFromFdName(module.Item);
ship.use(ship.standard[4], powerdistributor, true);
ship.standard[4].enabled = module.On;
ship.standard[4].priority = module.Priority;
if (module.Engineering) _addModifications(powerdistributor, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'radar':
const sensors = _moduleFromFdName(module.Item);
ship.use(ship.standard[5], sensors, true);
ship.standard[5].enabled = module.On;
ship.standard[5].priority = module.Priority;
if (module.Engineering) _addModifications(sensors, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'fueltank':
const fueltank = _moduleFromFdName(module.Item);
ship.use(ship.standard[6], fueltank, true);
ship.standard[6].enabled = true;
ship.standard[6].priority = 0;
break;
default:
}
if (module.Slot.toLowerCase().search(/hardpoint/) !== -1) {
// Add hardpoints
let hardpoint;
let hardpointClassNum = -1;
let hardpointSlotNum = -1;
let hardpointArrayNum = 0;
for (let i in shipTemplate.slots.hardpoints) {
if (shipTemplate.slots.hardpoints[i] === hardpointClassNum) {
// Another slot of the same class
hardpointSlotNum++;
} else {
// The first slot of a new class
hardpointClassNum = shipTemplate.slots.hardpoints[i];
hardpointSlotNum = 1;
}
// Now that we know what we're looking for, find it
const hardpointName = HARDPOINT_NUM_TO_CLASS[hardpointClassNum] + 'Hardpoint' + hardpointSlotNum;
const hardpointSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === hardpointName.toLowerCase());
if (!hardpointSlot) {
// This can happen with old imports that don't contain new hardpoints
} else if (!hardpointSlot) {
// No module
} else {
hardpoint = _moduleFromFdName(hardpointSlot.Item);
ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true);
ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On;
ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority;
modsToAdd.push({ coriolisMod: hardpoint, json: hardpointSlot });
}
hardpointArrayNum++;
}
}
}
let internalSlotNum = 0;
let militarySlotNum = 1;
for (let i in shipTemplate.slots.internal) {
if (!shipTemplate.slots.internal.hasOwnProperty(i)) {
continue;
}
const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'Military' : false;
// The internal slot might be a standard or a military slot. Military slots have a different naming system
let internalSlot = null;
if (isMilitary) {
const internalName = 'Military0' + militarySlotNum;
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
militarySlotNum++;
} else {
// Slot numbers are not contiguous so handle skips.
for (; internalSlot === null && internalSlotNum < 99; internalSlotNum++) {
// Slot sizes have no relationship to the actual size, either, so check all possibilities
for (let slotsize = 0; slotsize < 9; slotsize++) {
const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + slotsize;
if (json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase())) {
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
break;
}
}
}
}
if (!internalSlot) {
// This can happen with old imports that don't contain new slots
} else {
const internalJson = internalSlot;
const internal = _moduleFromFdName(internalJson.Item);
ship.use(ship.internal[i], internal, true);
ship.internal[i].enabled = internalJson.On === true;
ship.internal[i].priority = internalJson.Priority;
modsToAdd.push({ coriolisMod: internal, json: internalSlot });
}
}
for (const i of modsToAdd) {
if (i.json.Engineering) {
_addModifications(i.coriolisMod, i.json.Engineering.Modifiers, i.json.Engineering.BlueprintName, i.json.Engineering.Level, i.json.Engineering.ExperimentalEffect);
}
}
// We don't have any information on it so guess it's priority 5 and disabled
if (!ship.cargoHatch) {
ship.cargoHatch.enabled = false;
ship.cargoHatch.priority = 4;
}
// Now update the ship's codes before returning it
return ship.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString();
}
/**
* Add the modifications for a module
* @param {Module} module the module
* @param {Object} modifiers the modifiers
* @param {Object} blueprint the blueprint of the modification
* @param {Object} grade the grade of the modification
* @param {Object} specialModifications special modification
*/
function _addModifications(module, modifiers, blueprint, grade, specialModifications) {
if (!modifiers) return;
let special;
if (specialModifications) {
special = Modifications.specials[specialModifications];
}
// Add the blueprint definition, grade and special
if (blueprint) {
module.blueprint = getBlueprint(blueprint, module);
if (grade) {
module.blueprint.grade = Number(grade);
}
if (special) {
module.blueprint.special = special;
}
}
for (const i in modifiers) {
// Some special modifications
// Look up the modifiers to find what we need to do
const findMod = val => Object.keys(Modifications.modifierActions).find(elem => elem.toString().toLowerCase().replace(/(outfittingfieldtype_|persecond)/igm, '') === val.toString().toLowerCase().replace(/(outfittingfieldtype_|persecond)/igm, ''));
const modifierActions = Modifications.modifierActions[findMod(modifiers[i].Label)];
// TODO: Figure out how to scale this value.
if (!!modifiers[i].LessIsGood) {
}
let value = (modifiers[i].Value / modifiers[i].OriginalValue * 100 - 100) * 100;
if (value === Infinity) {
value = modifiers[i].Value * 100;
}
if (modifiers[i].Label.search('Resistance') >= 0) {
value = (modifiers[i].Value * 100) - (modifiers[i].OriginalValue * 100);
}
if (modifiers[i].Label.search('ShieldMultiplier') >= 0 || modifiers[i].Label.search('DefenceModifierHealthMultiplier') >= 0) {
value = ((100 + modifiers[i].Value) / (100 + modifiers[i].OriginalValue) * 100 - 100) * 100;
}
// Carry out the required changes
for (const action in modifierActions) {
if (isNaN(modifierActions[action])) {
module.setModValue(action, modifierActions[action]);
} else {
module.setModValue(action, value, true);
}
}
}
}
import Ship from '../shipyard/Ship';
import { HARDPOINT_NUM_TO_CLASS, shipModelFromJson } from './CompanionApiUtils';
import { Ships } from 'coriolis-data/dist';
import Module from '../shipyard/Module';
import { Modules } from 'coriolis-data/dist';
import { Modifications } from 'coriolis-data/dist';
import { getBlueprint, setQualityCB } from './BlueprintFunctions';
/**
* Check if an imported module is valid
* @param {Object} module the module to check
* @param {Object} moduleType the type of module to check
* @return {boolean} true if the module is valid
*/
function _isValidImportedModule(module, moduleType) {
// First of all, has the _moduleFromFdName function returned 'null'?
if (!module){
return false
}
else {
return true
}
}
/**
* Obtain a module given its FD Name
* @param {string} fdname the FD Name of the module
* @return {Module} the module
*/
function _moduleFromFdName(fdname) {
if (!fdname) return null;
fdname = fdname.toLowerCase();
// Check standard modules
for (const grp in Modules.standard) {
if (Modules.standard.hasOwnProperty(grp)) {
for (const i in Modules.standard[grp]) {
if (Modules.standard[grp][i].symbol && Modules.standard[grp][i].symbol.toLowerCase() === fdname) {
// Found it
return new Module({ template: Modules.standard[grp][i] });
}
}
}
}
// Check hardpoint modules
for (const grp in Modules.hardpoints) {
if (Modules.hardpoints.hasOwnProperty(grp)) {
for (const i in Modules.hardpoints[grp]) {
if (Modules.hardpoints[grp][i].symbol && Modules.hardpoints[grp][i].symbol.toLowerCase() === fdname) {
// Found it
return new Module({ template: Modules.hardpoints[grp][i] });
}
}
}
}
// Check internal modules
for (const grp in Modules.internal) {
if (Modules.internal.hasOwnProperty(grp)) {
for (const i in Modules.internal[grp]) {
if (Modules.internal[grp][i].symbol && Modules.internal[grp][i].symbol.toLowerCase() === fdname) {
// Found it
return new Module({ template: Modules.internal[grp][i] });
}
}
}
}
// Not found
return null;
}
/**
* Build a ship from the journal Loadout event JSON
* @param {object} json the Loadout event JSON
* @return {Ship} the built ship
*/
export function shipFromLoadoutJSON(json) {
// Start off building a basic ship
const shipModel = shipModelFromJson(json);
if (!shipModel) {
throw 'No such ship found: "' + json.Ship + '"';
}
const shipTemplate = Ships[shipModel];
let ship = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots);
ship.buildWith(null);
// Initial Ship building, don't do engineering yet.
let modsToAdd = [];
for (const module of json.Modules) {
switch (module.Slot.toLowerCase()) {
// Cargo Hatch.
case 'cargohatch':
ship.cargoHatch.enabled = module.On;
ship.cargoHatch.priority = module.Priority;
break;
// Add the bulkheads
case 'armour':
if (module.Item.toLowerCase().endsWith('_armour_grade1')) {
ship.useBulkhead(0, true);
} else if (module.Item.toLowerCase().endsWith('_armour_grade2')) {
ship.useBulkhead(1, true);
} else if (module.Item.toLowerCase().endsWith('_armour_grade3')) {
ship.useBulkhead(2, true);
} else if (module.Item.toLowerCase().endsWith('_armour_mirrored')) {
ship.useBulkhead(3, true);
} else if (module.Item.toLowerCase().endsWith('_armour_reactive')) {
ship.useBulkhead(4, true);
} else {
throw 'Unknown bulkheads "' + module.Item + '"';
}
ship.bulkheads.enabled = true;
if (module.Engineering) _addModifications(ship.bulkheads.m, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'powerplant':
let powerplant = _moduleFromFdName(module.Item);
// Check the powerplant returned is valid
if (!_isValidImportedModule(powerplant, 'powerplant'))
{
powerplant = _moduleFromFdName('Int_Missing_Powerplant');
module.Engineering = null;
}
ship.use(ship.standard[0], powerplant, true);
ship.standard[0].enabled = module.On;
ship.standard[0].priority = module.Priority;
if (module.Engineering) _addModifications(powerplant, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'mainengines':
let thrusters = _moduleFromFdName(module.Item);
// Check the thrusters returned is valid
if (!_isValidImportedModule(thrusters, 'thrusters'))
{
thrusters = _moduleFromFdName('Int_Missing_Engine');
module.Engineering = null;
}
ship.use(ship.standard[1], thrusters, true);
ship.standard[1].enabled = module.On;
ship.standard[1].priority = module.Priority;
if (module.Engineering) _addModifications(thrusters, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'frameshiftdrive':
let frameshiftdrive = _moduleFromFdName(module.Item);
// Check the frameshiftdrive returned is valid
if (!_isValidImportedModule(frameshiftdrive, 'frameshiftdrive'))
{
frameshiftdrive = _moduleFromFdName('Int_Missing_Hyperdrive');
module.Engineering = null;
}
ship.use(ship.standard[2], frameshiftdrive, true);
ship.standard[2].enabled = module.On;
ship.standard[2].priority = module.Priority;
if (module.Engineering) _addModifications(frameshiftdrive, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'lifesupport':
let lifesupport = _moduleFromFdName(module.Item);
// Check the lifesupport returned is valid
if (!_isValidImportedModule(lifesupport, 'lifesupport'))
{
lifesupport = _moduleFromFdName('Int_Missing_LifeSupport');
module.Engineering = null;
}
ship.use(ship.standard[3], lifesupport, true);
ship.standard[3].enabled = module.On === true;
ship.standard[3].priority = module.Priority;
if (module.Engineering) _addModifications(lifesupport, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'powerdistributor':
let powerdistributor = _moduleFromFdName(module.Item);
// Check the powerdistributor returned is valid
if (!_isValidImportedModule(powerdistributor, 'powerdistributor'))
{
powerdistributor = _moduleFromFdName('Int_Missing_PowerDistributor');
module.Engineering = null;
}
ship.use(ship.standard[4], powerdistributor, true);
ship.standard[4].enabled = module.On;
ship.standard[4].priority = module.Priority;
if (module.Engineering) _addModifications(powerdistributor, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'radar':
let sensors = _moduleFromFdName(module.Item);
// Check the sensors returned is valid
if (!_isValidImportedModule(sensors, 'sensors'))
{
sensors = _moduleFromFdName('Int_Missing_Sensors');
module.Engineering = null;
}
ship.use(ship.standard[5], sensors, true);
ship.standard[5].enabled = module.On;
ship.standard[5].priority = module.Priority;
if (module.Engineering) _addModifications(sensors, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'fueltank':
let fueltank = _moduleFromFdName(module.Item);
// Check the fueltank returned is valid
if (!_isValidImportedModule(fueltank, 'fueltank'))
{
fueltank = _moduleFromFdName('Int_Missing_FuelTank');
}
ship.use(ship.standard[6], fueltank, true);
ship.standard[6].enabled = true;
ship.standard[6].priority = 0;
break;
default:
}
if (module.Slot.toLowerCase().search(/hardpoint/) !== -1) {
// Add hardpoints
let hardpoint;
let hardpointClassNum = -1;
let hardpointSlotNum = -1;
let hardpointArrayNum = 0;
for (let i in shipTemplate.slots.hardpoints) {
if (shipTemplate.slots.hardpoints[i] === hardpointClassNum) {
// Another slot of the same class
hardpointSlotNum++;
} else {
// The first slot of a new class
hardpointClassNum = shipTemplate.slots.hardpoints[i];
hardpointSlotNum = 1;
}
// Now that we know what we're looking for, find it
const hardpointName = HARDPOINT_NUM_TO_CLASS[hardpointClassNum] + 'Hardpoint' + hardpointSlotNum;
const hardpointSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === hardpointName.toLowerCase());
if (!hardpointSlot) {
// This can happen with old imports that don't contain new hardpoints
} else {
hardpoint = _moduleFromFdName(hardpointSlot.Item);
// Check the hardpoint module returned is valid
if (!_isValidImportedModule(hardpoint, 'hardpoint')){
// Check if it's a Utility or Hardpoint
if (hardpointSlot.Slot.toLowerCase().search(/tiny/))
{
// Use the missing_hardpoint module 'Missing Hardpoint' which will inform the user that the module is missing
hardpoint = _moduleFromFdName('Hpt_Missing_Hardpoint');
}
else {
// Use the missing_hardpoint module 'Missing Utility' which will inform the user that the module is missing
hardpoint = _moduleFromFdName('Hpt_Missing_Utility');
}
ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true);
ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On;
ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority;
} else {
ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true);
ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On;
ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority;
modsToAdd.push({ coriolisMod: hardpoint, json: hardpointSlot });
}
}
hardpointArrayNum++;
}
}
}
let internalSlotNum = 0;
let militarySlotNum = 1;
for (let i in shipTemplate.slots.internal) {
if (!shipTemplate.slots.internal.hasOwnProperty(i)) {
continue;
}
const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'Military' : false;
const isPlanetary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'PlanetaryApproachSuite' : false;
// The internal slot might be a standard or a military slot, or a planetary slot. Military and Planetary slots have a different naming system
let internalSlot = null;
if (isMilitary) {
const internalName = 'Military0' + militarySlotNum;
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
militarySlotNum++;
} else if (isPlanetary) {
const internalName = 'PlanetaryApproachSuite';
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
} else {
// Slot numbers are not contiguous so handle skips.
for (; internalSlot === null && internalSlotNum < 99; internalSlotNum++) {
// Slot sizes have no relationship to the actual size, either, so check all possibilities
for (let slotsize = 0; slotsize < 9; slotsize++) {
const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + slotsize;
if (json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase())) {
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
break;
}
}
}
}
if (!internalSlot) {
// This can happen with old imports that don't contain new slots
} else {
const internalJson = internalSlot;
let internal = _moduleFromFdName(internalJson.Item);
// Check the internal module returned is valid
if (!_isValidImportedModule(internal, 'internal'))
{
internal = _moduleFromFdName('Int_Missing_Module');
ship.use(ship.internal[i], internal, true);
ship.internal[i].enabled = internalJson.On === true;
ship.internal[i].priority = internalJson.Priority;
//throw 'Unknown internal module: "' + module.Item + '"';
}
else {
ship.use(ship.internal[i], internal, true);
ship.internal[i].enabled = internalJson.On === true;
ship.internal[i].priority = internalJson.Priority;
modsToAdd.push({ coriolisMod: internal, json: internalSlot });
}
}
}
for (const i of modsToAdd) {
if (i.json.Engineering) {
_addModifications(i.coriolisMod, i.json.Engineering.Modifiers, i.json.Engineering.Quality, i.json.Engineering.BlueprintName, i.json.Engineering.Level, i.json.Engineering.ExperimentalEffect);
}
}
// We don't have any information on it so guess it's priority 5 and disabled
if (!ship.cargoHatch) {
ship.cargoHatch.enabled = false;
ship.cargoHatch.priority = 4;
}
// Now update the ship's codes before returning it
return ship.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString();
}
/**
* Add the modifications for a module
* @param {Module} module the module
* @param {Object} modifiers the modifiers
* @param {float} quality quality of the modifiers 0 to 1
* @param {Object} blueprint the blueprint of the modification
* @param {Object} grade the grade of the modification
* @param {Object} specialModifications special modification
*/
function _addModifications(module, modifiers, quality, blueprint, grade, specialModifications) {
if (!modifiers && !quality) return;
let special;
if (specialModifications) {
if (specialModifications == 'special_plasma_slug') {
if (module.symbol.match(/PlasmaAccelerator/i)) {
specialModifications = 'special_plasma_slug_pa';
} else {
specialModifications = 'special_plasma_slug_cooled';
}
}
special = Modifications.specials[specialModifications];
}
// Add the blueprint definition, grade and special
if (blueprint) {
module.blueprint = getBlueprint(blueprint, module);
if (grade) {
module.blueprint.grade = Number(grade);
}
if (special) {
module.blueprint.special = special;
}
}
if (modifiers) {
for (const i in modifiers) {
// Some special modifications
// Look up the modifiers to find what we need to do
const findMod = val => Object.keys(Modifications.modifierActions).find(elem => elem.toString().toLowerCase().replace(/(outfittingfieldtype_|persecond)/igm, '') === val.toString().toLowerCase().replace(/(outfittingfieldtype_|persecond)/igm, ''));
const modifierActions = Modifications.modifierActions[findMod(modifiers[i].Label)];
// TODO: Figure out how to scale this value.
if (!!modifiers[i].LessIsGood) {
}
let value = (modifiers[i].Value / modifiers[i].OriginalValue * 100 - 100) * 100;
if (value === Infinity) {
value = modifiers[i].Value * 100;
}
if (modifiers[i].Label.search('DamageFalloffRange') >= 0) {
value = (modifiers[i].Value / module.range - 1) * 100;
}
if (modifiers[i].Label.search('Resistance') >= 0) {
value = (modifiers[i].Value * 100) - (modifiers[i].OriginalValue * 100);
}
if (modifiers[i].Label.search('ShieldMultiplier') >= 0 || modifiers[i].Label.search('DefenceModifierHealthMultiplier') >= 0) {
value = ((100 + modifiers[i].Value) / (100 + modifiers[i].OriginalValue) * 100 - 100) * 100;
}
// Carry out the required changes
for (const action in modifierActions) {
if (isNaN(modifierActions[action])) {
module.setModValue(action, modifierActions[action]);
} else {
module.setModValue(action, value, true);
}
}
}
} else if (quality) {
setQualityCB(module.blueprint, quality, (featureName, value) => module.setModValue(featureName, value, false));
}
}

View File

@@ -105,7 +105,7 @@ function orbisShorten(url, success, error) {
}
}
const API_ORBIS = 'https://orbis.zone/api/builds/add';
const API_ORBIS = 'https://api.orbis.zone/ships';
/**
* Upload to Orbis
* @param {object} ship The URL to shorten

View File

@@ -1,6 +1,7 @@
import React from 'react';
import Persist from '../stores/Persist';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
/**
* Determine if a slot on a ship can mount a module of a particular class and group
@@ -139,20 +140,21 @@ function diff(format, mVal, mmVal) {
export function diffDetails(language, m, mm) {
let { formats, translate, units } = language;
let propDiffs = [];
m = new Module(m);
// Module-specific items
if (m.grp === 'pp') {
let mPowerGeneration = m.pgen || 0;
let mPowerGeneration = m.getPowerGeneration() || 0;
let mmPowerGeneration = mm ? mm.getPowerGeneration() : 0;
if (mPowerGeneration != mmPowerGeneration) propDiffs.push(<div key='pgen'>{translate('pgen')}: <span className={diffClass(mPowerGeneration, mmPowerGeneration)}>{diff(formats.round, mPowerGeneration, mmPowerGeneration)}{units.MW}</span></div>);
} else {
let mPowerUsage = m.power || 0;
let mPowerUsage = m.getPowerUsage() || 0;
let mmPowerUsage = mm ? mm.getPowerUsage() || 0 : 0;
if (mPowerUsage != mmPowerUsage) propDiffs.push(<div key='power'>{translate('power')}: <span className={diffClass(mPowerUsage, mmPowerUsage, true)}>{diff(formats.round, mPowerUsage, mmPowerUsage)}{units.MW}</span></div>);
}
let mDps = m.damage * (m.rpshot || 1) * (m.rof || 1);
let mDps = m.getDps() || 0;
let mmDps = mm ? mm.getDps() || 0 : 0;
if (mDps && mDps != mmDps) propDiffs.push(<div key='dps'>{translate('dps')}: <span className={diffClass(mmDps, mDps, true)}>{diff(formats.round, mDps, mmDps)}</span></div>);
@@ -164,7 +166,7 @@ export function diffDetails(language, m, mm) {
if (mAffectsShield) {
if (m.grp == 'sb') { // Both m and mm must be utility modules if this is true
newShield = this.calcShieldStrengthWith(null, m.shieldboost - (mm ? mm.getShieldBoost() || 0 : 0));
newShield = this.calcShieldStrengthWith(null, m.getShieldBoost() - (mm ? mm.getShieldBoost() || 0 : 0));
} else {
newShield = this.calcShieldStrengthWith(m);
}
@@ -179,7 +181,7 @@ export function diffDetails(language, m, mm) {
}
if (m.grp === 'mrp') {
let mProtection = m.protection;
let mProtection = m.getProtection();
let mmProtection = mm ? mm.getProtection() || 0 : 0;
if (mProtection != mmProtection) {
propDiffs.push(<div key='protection'>{translate('protection')}: <span className={diffClass(mmProtection, mProtection, true)}>{diff(formats.pct, mProtection, mmProtection)}</span></div>);
@@ -187,7 +189,7 @@ export function diffDetails(language, m, mm) {
}
if (m.grp === 'hr') {
let mHullReinforcement = m.hullreinforcement;
let mHullReinforcement = m.getHullReinforcement();
let mmHullReinforcement = mm ? mm.getHullReinforcement() || 0 : 0;
if (mHullReinforcement && mHullReinforcement != mmHullReinforcement) propDiffs.push(<div key='hullreinforcement'>{translate('hullreinforcement')}: <span className={diffClass(mmHullReinforcement, mHullReinforcement, true)}>{diff(formats.round, mHullReinforcement, mmHullReinforcement)}</span></div>);
}
@@ -219,7 +221,7 @@ export function diffDetails(language, m, mm) {
let mmCost = mm ? mm.cost : 0;
if (mCost != mmCost) propDiffs.push(<div key='cost'>{translate('cost')}: <span className={diffClass(mCost, mmCost, true) }>{formats.int(mCost ? Math.round(mCost * (1 - Persist.getModuleDiscount())) : 0)}{units.CR}</span></div>);
let mMass = m.mass || 0;
let mMass = m.getMass() || 0;
let mmMass = mm ? mm.getMass() : 0;
if (mMass != mmMass) propDiffs.push(<div key='mass'>{translate('mass')}: <span className={diffClass(mMass, mmMass, true)}>{diff(formats.round, mMass, mmMass)}{units.T}</span></div>);
@@ -240,7 +242,7 @@ export function diffDetails(language, m, mm) {
}
}
let mIntegrity = m.integrity || 0;
let mIntegrity = m.getIntegrity() || 0;
let mmIntegrity = mm ? mm.getIntegrity() || 0 : 0;
if (mIntegrity != mmIntegrity) {
propDiffs.push(<div key='integrity'>{translate('integrity')}: <span className={diffClass(mmIntegrity, mIntegrity, true)}>{diff(formats.round, mIntegrity, mmIntegrity)}</span></div>);

View File

@@ -8,6 +8,8 @@
<meta name="description" content="A ship builder, outfitting and comparison
tool for Elite Dangerous">
<meta name="mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=device-width, initial-scale=1.0,
maximum-scale=1.0, user-scalable=0">
<link rel="manifest" href="/manifest.json">
@@ -25,48 +27,25 @@
<meta name="msapplication-TileImage" content="/mstile-144x144.png">
<meta name="msapplication-config" content="/browserconfig.xml">
<meta name="theme-color" content="#000000">
<!-- <script data-ad-client="ca-pub-3709458261881414" async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> -->
<script>
window.CORIOLIS_GAPI_KEY = '<%- htmlWebpackPlugin.options.gapiKey %>';
window.CORIOLIS_VERSION = '<%- htmlWebpackPlugin.options.version %>';
window.CORIOLIS_DATE = '<%- htmlWebpackPlugin.options.date.toISOString().slice(0, 10) %>';
window.BUGSNAG_VERSION = '<%- htmlWebpackPlugin.options.version + '-' + htmlWebpackPlugin.options.date.toISOString() %>';
</script>
<% if (htmlWebpackPlugin.options.uaTracking) { %>
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', '<%- htmlWebpackPlugin.options.uaTracking %>', 'auto');
ga('send', 'pageview');
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-55840909-18"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-55840909-18');
</script>
<script async src='https://www.google-analytics.com/analytics.js'></script>
<% } %>
</head>
<body style="background-color:#000;">
<section id="coriolis">
<!-- Piwik -->
<!-- <script type="text/javascript">
var _paq = _paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(["setCookieDomain", "*.coriolis.edcd.io"]);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//stats.isadankme.me/";
_paq.push(['setTrackerUrl', u+'piwik.php']);
_paq.push(['setSiteId', '4']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
})();
</script>-->
<!-- End Piwik Code -->
<!-- Bugsnag -->
<script src="https://d2wy8f7a9ursnm.cloudfront.net/v5.0.0/bugsnag.min.js"></script>
<script>
window.bugsnagClient = bugsnag('ba9fae819372850fb660755341fa6ef5', {appVersion: window.BUGSNAG_VERSION || undefined})
window.Bugsnag = window.bugsnagClient
</script>
</head>
<body style="background-color:#000;">
<section id="coriolis"></section>
</body>
</html>
</section>
</body>
</html>

View File

@@ -2,6 +2,8 @@
@bgDarken: 40%;
@disabledDarken: 15%;
@bgTransparency: 10%;
@bgHighlight: 5%;
@fgHighlight: 10%;
// Foreground colors
@fg: #CCC;
@@ -21,9 +23,14 @@
@bgBlack: #000;
@primary-bg: fadeout(darken(@primary, 47%), 15%);
@alt-primary-bg: fadeout(darken(@primary, 42%), 15%); // Lighter brown background
@secondary-bg: fadeout(darken(@secondary, @bgDarken), @bgTransparency); // Brown background
@secondary-bg: fadeout(darken(@secondary, @bgDarken), @bgTransparency); // Blue background
@warning-bg: fadeout(darken(@warning, @bgDarken), @bgTransparency); // Dark Red
@alt-primary-bg-highlighted: lighten(@alt-primary-bg, @bgHighlight);
@fg-highlighted: lighten(@fg, @fgHighlight);
@primary-darker: darken(@primary, 30%);
.fg {
color: @fg;

View File

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

View File

@@ -49,4 +49,45 @@ a.ship {
font-size: 0.7em;
float: right;
}
}
}
.shipyard-table-wrapper {
white-space: nowrap;
margin: 0 auto;
font-size: 0.8em;
position: relative;
display: inline-block;
max-width: 100%;
}
table.shipyard-table{
tbody tr.comparehighlight{
background-color: @secondary-bg;
color: @fg-highlighted;
}
}
.shipyard-table-wrapper {
border-bottom: 1px solid @primary-darker;
}
.shipyard-table-wrapper div .shipyard-table td:last-child {
border-right: 1px solid @primary-darker;
}
.shipyard-table-wrapper > .shipyard-table td:first-child {
border-left: 1px solid @primary-darker;
}
.content-wrapper{
display: inline-block;
margin: 0 auto;
max-width: 100%;
}
.table-tools{
text-align: left;
color: @primary;
label{
cursor: pointer;
}
}

View File

@@ -52,7 +52,7 @@ tbody tr {
}
.no-touch &.highlight:hover, .no-touch &.highlighted {
background-color: @warning-bg;
background-color: @alt-primary-bg-highlighted; //@warning-bg;
}
&:nth-child(odd){
@@ -84,3 +84,5 @@ td {
text-align: center;
}
}

98
src/migrate.html Normal file
View File

@@ -0,0 +1,98 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Coriolis EDCD Edition</title>
<meta charset="UTF-8" />
<link rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[0] %>" />
<!-- Standard headers -->
<meta
name="description"
content="A ship builder, outfitting and comparison
tool for Elite Dangerous"
/>
<meta name="mobile-web-app-capable" content="yes" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0,
maximum-scale=1.0, user-scalable=0"
/>
<link rel="manifest" href="/manifest.json" />
<link rel="shortcut icon" href=/favicon2.ico>
<link
rel="icon"
sizes="152x152 192x192"
type="image/png"
href="/192x192.png"
/>
<!-- Apple/iOS headers -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Coriolis" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<!-- Microsoft Windows Phone/Tablet headers -->
<meta name="msapplication-TileColor" content="#000000" />
<meta name="msapplication-TileImage" content="/mstile-144x144.png" />
<meta name="msapplication-config" content="/browserconfig.xml" />
<meta name="theme-color" content="#000000" />
</head>
<body>
<body style="background-color:#000;">
<section id="coriolis">
<div class="modal">
<h2>
Please migrate to <a href="https://coriolis.io">coriolis.io</a>
</h2>
You are currently on coriolis.<strong>.edcd</strong>.io This domain is
considered deprecated. To migrate your builds, copy the below text and
go to
<a
target="_blank"
rel="noopener noreferrer"
href="https://coriolis.io"
>this link</a
>, press ctrl + i and then paste in the data. (If you are on mobile,
you can go to the settings and hit import)
<div>
<textarea id="data" class="cb json"></textarea>
</div>
</div>
</section>
</body>
<script>
const LS = localStorage;
/**
* Safe localstorage get string
* @param {String} key key
* @return {String} The stored string
*/
function _getString(key) {
return LS ? LS.getItem(key) : null;
}
/**
* Safe localstorage get
* @param {String} key key
* @return {object | number} The stored data
*/
function _get(key) {
let str = _getString(key);
try {
return str ? JSON.parse(str) : null;
} catch (e) {
return null;
}
}
const textarea = document.querySelector("#data");
const data = {
builds: _get("builds") || {},
comparisons: _get("comparisons") || {},
insurance: _get("insurance") || "standard",
shipDiscount: _get("shipDiscount") || 0,
moduleDiscount: _get("moduleDiscount") || 0
};
textarea.textContent = JSON.stringify(data, null, 2);
</script>
</body>
</html>

View File

@@ -1,45 +1,54 @@
import {precacheAndRoute, createHandlerBoundToURL} from 'workbox-precaching';
import {NavigationRoute, registerRoute} from 'workbox-routing';
import {StaleWhileRevalidate, CacheFirst} from 'workbox-strategies';
import {CacheableResponsePlugin} from 'workbox-cacheable-response'
import {ExpirationPlugin} from 'workbox-expiration';
console.log('Hello from sw.js');
if (workbox) {
console.log('Yay! Workbox is loaded 🎉');
workbox.precaching.precacheAndRoute(self.__precacheManifest);
// See https://developer.chrome.com/docs/workbox/migration/migrate-from-v4/ for guide to changes made
console.log('Yay! Workbox is loaded 🎉');
precacheAndRoute(self.__WB_MANIFEST || []);
workbox.routing.registerNavigationRoute('/index.html');
const handler = createHandlerBoundToURL('/index.html');
const navigationRoute = new NavigationRoute(handler
// , {allowlist: [...], denylist: [...],}
);
registerRoute(navigationRoute);
workbox.routing.registerRoute(
new RegExp('/(.*?)'),
workbox.strategies.staleWhileRevalidate({
plugins: [
new workbox.cacheableResponse.Plugin({
statuses: [0, 200]
})
]
})
);
workbox.routing.registerRoute(
new RegExp('https://fonts.(?:googleapis|gstatic).com/(.*)'),
workbox.strategies.cacheFirst({
cacheName: 'google-fonts',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 30
}),
new workbox.cacheableResponse.Plugin({
statuses: [0, 200]
})
]
})
);
registerRoute(
/\.(?:png|jpg|jpeg|svg|gif)$/,
new CacheFirst({
plugins: [
new CacheableResponsePlugin({
statuses: [0, 200]
})
]
})
);
try {
workbox.googleAnalytics.initialize();
} catch (e) {
console.log('Probably an ad-blocker');
}
} else {
console.log('Boo! Workbox didn\'t load 😬');
}
registerRoute(
/\.(?:js|css)$/,
new StaleWhileRevalidate({
cacheName: 'static-resources',
})
);
registerRoute(
new RegExp('https://fonts.(?:googleapis|gstatic).com/(.*)'),
new CacheFirst({
cacheName: 'google-fonts',
plugins: [
new ExpirationPlugin({
maxEntries: 30
}),
new CacheableResponsePlugin({
statuses: [0, 200]
})
]
})
);
self.addEventListener('message', event => {
if (!event.data) {

81
webpack.common.js Normal file
View File

@@ -0,0 +1,81 @@
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const pkgJson = require('./package');
const buildDate = new Date();
module.exports = {
entry: {
main: './src/app/index.js'
},
resolve: {
// When requiring, you don't need to add these extensions
extensions: ['.js', '.jsx', '.json', '.less'],
fallback: {
// Consider replacing brwoserify-zlib-next c. 2016 package with pako, which it's just a wrapper for
/* Some of these polyfills may not even be necessary, and were added in an attempt to deal with build issues
while upgrading to Webpack v5 */
"zlib": require.resolve("browserify-zlib-next"),
"assert": require.resolve("assert/"),
"buffer": require.resolve("buffer/"),
"stream": require.resolve("stream-browserify"),
/*
"url": require.resolve("url/"),
"path": require.resolve("path-browserify"),
"crypto": require.resolve("crypto-browserify"),
"os": require.resolve("os-browserify/browser"),
"https": require.resolve("https-browserify"),
"http": require.resolve("stream-http"),
"vm": require.resolve("vm-browserify"),
"constants": require.resolve("constants-browserify"),
// "fs": false
*/
}
},
optimization: {
usedExports: true
},
output: {
path: path.join(__dirname, 'build'),
chunkFilename: '[name].bundle.js',
// assetModuleFilename: '[contenthash][ext]',
publicPath: '/',
clean: true // we already do rimraf on the build dir, but this should obviate that
},
plugins: [
// new webpack.optimize.CommonsChunkPlugin({
// name: 'lib',
// filename: 'lib.js'
// }),
new HtmlWebpackPlugin({
inject: true,
template: path.join(__dirname, 'src/index.ejs'),
version: pkgJson.version,
// gapiKey: process.env.CORIOLIS_GAPI_KEY || '',
date: buildDate,
}),
new MiniCssExtractPlugin({
filename: 'app.css',
}),
// Solve missing Buffer polyfill that breaks module engineering
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
}),
],
module: {
rules: [
{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader' ]},
{
test: /\.less$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader' ]
},
{ test: /\.(js|jsx)$/, use: ['babel-loader'], include: path.join(__dirname, 'src') },
{
test: /\.(jpe?g|svg|png|gif|ico|eot|ttf|woff|woff2?)(\?v=\d+\.\d+\.\d+)?$/i,
type: 'asset/resource',
},
]
}
};

View File

@@ -1,69 +1,27 @@
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const WebpackNotifierPlugin = require('webpack-notifier');
const pkgJson = require('./package');
const buildDate = new Date();
const CopyWebpackPlugin = require('copy-webpack-plugin');
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = {
const CopyWebpackPlugin = require('copy-webpack-plugin');
const WebpackNotifierPlugin = require('webpack-notifier');
module.exports = merge(common, {
devtool: 'source-map',
devServer: {
headers: { 'Access-Control-Allow-Origin': '*' }
},
mode: 'development',
entry: {
main: './src/app/index.js',
},
resolve: {
// When requiring, you don't need to add these extensions
extensions: ['.js', '.jsx', '.json', '.less']
},
optimization: {
minimize: false,
usedExports: true
},
output: {
path: path.join(__dirname, 'build'),
chunkFilename: '[name].bundle.js',
publicPath: '/'
},
plugins: [
new CopyWebpackPlugin(['src/.htaccess', 'src/iframe.html', 'src/xdLocalStoragePostMessageApi.min.js']),
// new webpack.optimize.CommonsChunkPlugin({
// name: 'lib',
// filename: 'lib.js'
// }),
new HtmlWebpackPlugin({
inject: true,
template: path.join(__dirname, 'src/index.ejs'),
version: pkgJson.version,
date: buildDate,
gapiKey: process.env.CORIOLIS_GAPI_KEY || ''
}),
new ExtractTextPlugin({
filename: 'app.css',
disable: false,
allChunks: true
}),
new CopyWebpackPlugin({
patterns: [
'src/.htaccess',
'src/iframe.html',
'src/xdLocalStoragePostMessageApi.min.js'
]}),
new WebpackNotifierPlugin({ alwaysNotify: true }),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
],
module: {
rules: [
{ test: /\.css$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader' }) },
{
test: /\.less$/,
loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader!less-loader' })
},
{ test: /\.(js|jsx)$/, loaders: ['babel-loader'], include: path.join(__dirname, 'src') },
{ test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
{ test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
{ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream' },
{ test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader' },
{ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml' }
]
}
};
]
});

View File

@@ -1,54 +1,37 @@
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const { InjectManifest } = require('workbox-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const { BugsnagSourceMapUploaderPlugin, BugsnagBuildReporterPlugin } = require('webpack-bugsnag-plugins');
const pkgJson = require('./package');
const buildDate = new Date();
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = {
devtool: 'source-map',
entry: {
main: './src/app/index.js'
},
resolve: {
extensions: ['.js', '.jsx', '.json', '.less']
},
output: {
path: path.join(__dirname, 'build'),
chunkFilename: '[name].bundle.js',
publicPath: '/',
globalObject: 'this'
},
const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { InjectManifest } = require('workbox-webpack-plugin');
module.exports = merge(common, {
// devtool: 'source-map',
mode: 'production',
optimization: {
minimize: true,
usedExports: true
},
output: {
globalObject: 'this'
},
plugins: [
new CopyWebpackPlugin(['src/.htaccess', { from: 'src/schemas', to: 'schemas' }, {
from: 'src/images/logo/*',
flatten: true,
to: ''
}, 'src/iframe.html', 'src/xdLocalStoragePostMessageApi.min.js']),
// new webpack.optimize.CommonsChunkPlugin({
// name: 'lib',
// filename: 'lib.[chunkhash:6].js'
// }),
new HtmlWebpackPlugin({
inject: true,
template: path.join(__dirname, 'src/index.ejs'),
uaTracking: process.env.CORIOLIS_UA_TRACKING || '',
gapiKey: process.env.CORIOLIS_GAPI_KEY || '',
date: buildDate,
version: pkgJson.version
}),
new ExtractTextPlugin({
filename: '[hash:6].css',
disable: false,
allChunks: true
new CopyWebpackPlugin({
patterns: [
'src/.htaccess',
'src/iframe.html',
'src/xdLocalStoragePostMessageApi.min.js',
{ from: 'src/schemas', to: 'schemas' },
{
from: 'src/images/logo/*',
to: '[name][ext]'
}
]}),
/* new HtmlWebpackPlugin({
// uaTracking: process.env.CORIOLIS_UA_TRACKING || '',
}), */
new MiniCssExtractPlugin({
filename: '[contenthash:6].css',
}),
// new BugsnagBuildReporterPlugin({
// apiKey: 'ba9fae819372850fb660755341fa6ef5',
@@ -59,25 +42,11 @@ module.exports = {
// overwrite: true,
// appVersion: `${pkgJson.version}-${buildDate.toISOString()}`
// }),
new InjectManifest({
swSrc: './src/sw.js',
importWorkboxFrom: 'cdn',
swDest: 'service-worker.js'
}),
],
module: {
rules: [
{ test: /\.css$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader' }) },
{
test: /\.less$/,
loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader!less-loader' })
},
{ test: /\.(js|jsx)$/, loader: 'babel-loader?cacheDirectory=true', include: path.join(__dirname, 'src') },
{ test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
{ test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
{ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream' },
{ test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader' },
{ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml' }
]
}
};
]
});