Compare commits

...

1389 Commits

Author SHA1 Message Date
Felix Linker
25c30e46a6 Update detailed export to SLEF 2021-12-28 19:11:49 +01:00
Felix Linker
870c033c64 Make damage charts larger 2021-12-28 19:01:19 +01:00
Felix Linker
46b0200b28 Merge branch 'develop' into ed-forge 2021-12-28 18:58:52 +01:00
Felix Linker
e8bcb18f12 Dependency management 2021-12-28 18:41:47 +01:00
Felix Linker
68ca24ef96 Add module selection tooltip 2021-12-28 17:52:26 +01:00
Felix Linker
7ca2754934 Remove some NaNs from ship summary 2021-12-28 15:10:39 +01:00
Felix Linker
e82ab0aec0 Update multiple-slot actions to ed-forge 2021-12-28 00:38:51 +01:00
Felix Linker
d406018f0b Migrate ed-forge API change 2021-12-28 00:38:51 +01:00
Felix Linker
2438aa1f48 Remove tests 2021-12-27 16:16:41 +01:00
Felix Linker
c5bbeacc6a Update ed-forge import paths 2021-12-27 16:14:50 +01:00
Felix Linker
d9763f2db7 Remove unused code 2021-12-26 23:17:45 +01:00
Felix Linker
5692cc6fe3 Remove unused setting module resistances 2021-12-26 23:12:41 +01:00
Felix Linker
22cdfcdecf Fix settings menu 2021-12-26 23:09:23 +01:00
Felix Linker
3db3b09c22 Remove comparison page 2021-12-26 23:01:17 +01:00
Felix Linker
23c264e321 Drop calculations made redundant by ed-forge 2021-12-26 21:48:29 +01:00
Felix Linker
d2c3787165 Disable announcements 2021-12-26 18:13:06 +01:00
Felix Linker
1e18d6e463 Update header 2021-12-26 18:09:36 +01:00
Felix Linker
044fea2d33 Update material button 2021-12-26 16:16:36 +01:00
Felix Linker
bc865f534b Don't give material icon fill color in svg 2021-12-26 15:01:54 +01:00
Felix Linker
796694a4e0 Fix station that sells this build 2021-12-25 17:57:37 +01:00
Felix Linker
829815a40b Fix export 2021-12-25 17:50:53 +01:00
Felix Linker
7a2c849ece Show module tooltips using Module.try 2021-12-25 17:12:31 +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
2eed1bc85b Add time to deplete armour/shields 2021-05-15 20:21:00 +02:00
Felix Linker
3469af10b6 Update ShipPicker 2021-05-15 15:23:36 +02:00
Felix Linker
629ba35bc5 Update engagement range and slider 2021-05-15 14:58:10 +02:00
Felix Linker
c19ca6648d Update README 2021-05-14 10:37:16 +02:00
Felix Linker
e453ff73b7 Migrate changes to slot-representation 2021-05-14 10:22:28 +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
c73ce1c234 Fix: don't cast mount symbol in available modules to string 2021-05-10 21:13:02 +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
00d3a93b91 Re-design shipyard page
Closes #577
2021-05-09 20:09:15 +02:00
Felix Linker
d4e612cb61 Display module selection for alloys correctly
Closes #579
2021-05-09 18:42:37 +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
c07cfc6e70 Don't round integers 2021-02-01 22:52:04 +01:00
Felix Linker
c4c6d32a5d Skip properties that do not apply to a module in blueprint tooltip 2021-02-01 22:41:41 +01:00
Felix Linker
a65bb06754 Implement tooltips for experimental effects 2021-02-01 22:40:01 +01:00
Felix Linker
23548e7c5c Remove redundant code 2021-02-01 22:14:22 +01:00
Felix Linker
74e6f54e19 Improve blueprint tooltips 2021-02-01 22:10:09 +01:00
Felix Linker
a46f8f97f6 Show blueprint grade in ascending order 2021-02-01 22:02:37 +01:00
Felix Linker
44dbdb1703 Don't show mass twice and allow to disable showing mass 2021-02-01 21:56:35 +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
187c5dae4a Implement blueprint tooltips 2021-01-10 22:57:33 +01:00
Felix Linker
07d324a3fa Add key to property header in ModificationsMenu 2021-01-10 22:02:00 +01:00
Felix Linker
5c63afd96c Add stats mapper to show certain synthetic props in ModificationsMenu 2021-01-10 22:01:21 +01:00
Felix Linker
53c40ac9c4 Fix indentation 2021-01-10 21:59:22 +01:00
Felix Linker
a180cbfdd4 Fix: showProp is not required in <Modification> 2021-01-10 21:58:35 +01:00
Felix Linker
2bb55d2c36 Remove donation references 2021-01-06 12:11:44 +01:00
Felix Linker
fc1524a943 Support non-percent modifiers 2021-01-03 12:32:01 +01:00
Felix Linker
c3747e4e5e Add toggle for properties in overview 2021-01-03 10:17:10 +01:00
Felix Linker
ab153981c9 Rework Modifictaions(Menu) 2021-01-02 10:59:58 +01:00
Felix Linker
a970e052c1 Update react-number-editor
Mitigates a bug where non-rounded numbers were shown to the user.
2021-01-02 10:55:16 +01:00
Felix Linker
df7e264a02 Use package-lock.json
Otherwise `npm audit` does not work.
2021-01-02 10:54:44 +01:00
Felix Linker
cdcda004f3 Implement units in modifications menu 2020-12-31 18:54:36 +01:00
yevhenii.chubar
49c827b2c8 Corrected calculations of modification values 2020-12-29 17:47:10 +02:00
Felix Linker
20e448fc0a Optimize constructors 2020-12-29 16:39:18 +01:00
Felix Linker
436e626c42 Group module selection by category 2020-12-29 16:37:28 +01:00
Felix Linker
3dd4675a0b Hide searchbar for core internal modules 2020-12-29 16:36:43 +01:00
Felix Linker
d3766d9e17 Migrate ed-forge API change 2020-12-29 13:18:57 +01:00
Felix Linker
d987c08ac8 Ship summary key for ship type is id 2020-12-29 12:23:23 +01:00
Felix Linker
cf50537e3d Fix diminishing returns for alloys
Closes #483
2020-11-16 23:27:24 +01:00
Felix Linker
f865ef6c6c Fix retrofit cost 2020-11-01 19:54:48 +01:00
Felix Linker
f2b7daac82 MovementProfile code includes pips for re-render on state change 2020-11-01 18:29:04 +01:00
Felix Linker
832bc488b6 Fix excluding modules from costs 2020-11-01 18:25:28 +01:00
Felix Linker
f513166d6c Rework boost button 2020-11-01 17:56:33 +01:00
Felix Linker
61fd0eb991 Rework profiles section 2020-11-01 16:01:53 +01:00
Felix Linker
1e51e7d4a6 Rework WeaponDamageChart 2020-11-01 15:58:28 +01:00
Felix Linker
4943d36bb8 Rework FSD profile 2020-11-01 02:59:37 +01:00
Felix Linker
9271d1fa09 Rework Movement graph 2020-11-01 02:32:52 +01:00
Felix Linker
8a09d94dfa Rework EngineProfile 2020-11-01 02:19:59 +01:00
Felix Linker
c44925dd62 Show normal speed in summary 2020-10-31 13:03:10 +01:00
Felix Linker
d006bbcb0f Handle no shield 2020-10-31 13:02:51 +01:00
Felix Linker
14453f6b80 Rework defence tab 2020-10-24 17:47:17 +02: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
felixlinker
8c267150a9 Rework Offence tab 2020-08-13 20:20:22 +02:00
felixlinker
ed60a78be0 Priority groups are zero indexed 2020-08-09 19:00:57 +02:00
felixlinker
82142b0cb1 Use Module.setEnabled in Power Management table 2020-08-09 19:00:45 +02:00
felixlinker
d8949fedb2 Rework PIP menu 2020-08-09 18:39:39 +02:00
felixlinker
cf72bd11a8 Replace manual bind(this) calls with auto-bind 2020-08-09 17:41:15 +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
Felix Linker
16ef7ea389 Rework cost section 2020-04-30 14:25:07 +02:00
Felix Linker
ff455e349e Implement coding standards 2020-04-16 15:22:07 +02:00
Felix Linker
ba9e7f1a32 Add code props variable as ship hash to update changes 2020-04-16 15:12:38 +02:00
Felix Linker
904498b20c Make power stats working 2020-04-15 19:59:50 +02:00
Thomas Hurst
13ec027772 Calculate jump range with a full reserve fuel tank (#560) 2020-04-10 18:52:39 +00:00
Felix Linker
409be7374c Make outfitting page working 2020-04-10 13:19:53 +02:00
Felix Linker
00c525e6ab Update shipyard-page to ed-forge 2020-04-10 12:50:56 +02: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
felixlinker
a2f52c03a1 Move hardpoint class for module selection into leaves of HTML tree 2019-10-10 18:18:18 +02:00
felixlinker
037df6b166 Remove InternalSlot component 2019-10-10 16:42:14 +02:00
felixlinker
90ab5b4b0a Remove component HardpointSlot 2019-10-10 16:30:51 +02:00
felixlinker
7bbfa8c43f Remove component StandardSlot 2019-10-10 16:11:54 +02:00
felixlinker
2fbcd158cc Rewrite ModificationsMenu 2019-10-10 16:06:41 +02:00
felixlinker
33c201800e Rewrite AvailableModulesMenu.jsx 2019-10-08 22:53:31 +02:00
Felix Linker
9797a8d781 First steps towards ed-forge rewrite 2019-10-08 15:02:16 +02: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
Mingming Cui
435c1b6d45 Chinese translation (#481)
* Chinese translation

* Added Chinese translation for UI

* Made some strings translatable.

Changed the translation ID of certain words which were used with different meanings in difference places in order to achieve a more accurate translation.

* fixed en format

* Fixed one capitalization mistake

* removed vs folder
2019-03-05 08:31:29 +11:00
willyb321
c5c9abe588 Merge branch 'master' into develop 2019-02-27 08:24:25 +11:00
opl-
363735d36b Fix ships menu not sorted by name (#477) 2019-02-26 08:00:09 +11:00
William
2741e7701b Update .gitlab-ci.yml 2019-02-18 12:38:40 +11:00
neilser
3be442ea60 Fix wrong units for pgen and power in pop-ups (#468) 2019-01-31 08:14:35 +11:00
Willyb321
34cbeca201 Merge branch 'develop' 2019-01-24 07:40:39 +11:00
Willyb321
b37e73ead6 one build stage 2019-01-23 08:58:12 +11:00
Willyb321
ee775521d6 Merge branch 'develop' of gitlab.willb.info:coriolis/coriolis into develop 2019-01-23 08:57:33 +11:00
Willyb321
5f84aaef1b add redirect from .edcd.io to .io 2019-01-23 08:57:17 +11:00
William
99ac58d999 Update .gitlab-ci.yml 2019-01-22 19:53:07 +11:00
William
f128a1e87d Create .gitlab-ci.yml 2019-01-22 19:45:28 +11:00
William
8c0768b451 Add license file using license from README 2019-01-22 09:30:20 +11:00
Felix Linker
319307136c Remove constraints for modification of maxmass 2019-01-15 19:17:45 +01:00
Felix Linker
a498452943 Multiple bug fixes (#463)
* Don't allow manually modifying multiplicative mods with base value zero

* Add missing argument when calculating fullTankRange

* Use opponent PD when calculating how long shields will hold

Closes #430

* Allow modifying max mass ONLY for shield generators

Closes #453
2019-01-15 10:32:33 +11:00
Willyb321
4b854b8305 Search tweaks 2019-01-04 09:28:21 +11:00
William Blythe
b400db8216 only cache if not 404 2019-01-04 08:01:02 +11:00
Willyb321
fb811faf5e Merge branch 'develop' 2019-01-03 08:54:49 +11:00
Willyb321
deeb525433 Remove dynamic import for pages
Closes #441
2019-01-03 08:54:22 +11:00
Willyb321
1cb88115f6 Merge branch 'develop' 2019-01-03 08:46:29 +11:00
Willyb321
a181791500 We don't run ads now. 2019-01-03 08:45:20 +11:00
William Blythe
94eec120da Merge branch 'develop' 2019-01-02 12:13:46 +11:00
William Blythe
48092d4395 trycatch announcements check 2019-01-02 12:13:27 +11:00
William Blythe
2457c30b94 Merge branch 'develop' 2018-12-31 08:19:07 +11:00
William Blythe
593f069806 Fix title being page not found even after page has loaded 2018-12-31 08:18:36 +11:00
William Blythe
a073692632 add probe radius 2018-12-27 08:18:38 +11:00
William Blythe
7752d5c9db Fix #445 2018-12-26 09:23:44 +11:00
William Blythe
544e5acaef remove snow 2018-12-26 09:14:45 +11:00
William Blythe
9ab35bbaf9 Merge branch 'develop' 2018-12-26 09:07:21 +11:00
William Blythe
98782da200 make shipyardpage not dynamic import 2018-12-26 09:07:05 +11:00
William Blythe
2936364934 prep for abl 2018-12-26 08:50:00 +11:00
William Blythe
01e1609a9f Merge branch 'develop' 2018-12-26 08:30:00 +11:00
William Blythe
f85a03a9ae remove christmas theme
its 8:30am in sydney ok
2018-12-26 08:29:45 +11:00
William Blythe
2703c2aa23 move mining laser to new mining group 2018-12-25 09:35:01 +11:00
William Blythe
954921c231 probably fix scrolling 2018-12-25 09:07:39 +11:00
William Blythe
8bed35a8ba probably fix scrolling 2018-12-25 09:06:00 +11:00
William Blythe
9f4ae60577 add christmas snow
Credit: https://codepen.io/keithclark/pen/yBcsr
2018-12-25 08:53:50 +11:00
William Blythe
ee3c50e27d add christmas snow
Credit: https://codepen.io/keithclark/pen/yBcsr
2018-12-25 08:53:05 +11:00
Willyb321
03020743b3 some docker adjustments, fix brotli comprpession 2018-12-23 07:51:42 +11:00
William Blythe
001fed67b7 change order of compression 2018-12-23 06:51:50 +11:00
William Blythe
3894915740 fix nginx conf 2018-12-23 06:39:32 +11:00
William Blythe
68fd13e8dc /shrgu 2018-12-22 08:25:53 +11:00
William Blythe
fdf16cd959 maybe 2018-12-22 08:25:10 +11:00
William Blythe
d916c67fe0 f 2018-12-22 08:13:06 +11:00
William Blythe
d8a8e224f4 enable brotli 2018-12-22 08:07:20 +11:00
William Blythe
e1c115747c nginx compression 2018-12-22 08:03:35 +11:00
William Blythe
e9b6d71606 dynamically import pages, should make things a bit faster 2018-12-22 07:48:35 +11:00
William Blythe
e03e249d2f probably fix .dockerignore 2018-12-22 07:19:53 +11:00
monopoint
0cfb0b6878 Fix background highlight on sort in shipyard (#447)
* Fix background highlight on sort in shipyard

* removed docs line
2018-12-21 12:14:43 -08:00
William Blythe
600df162aa Prep for sub surface displacement missile 2018-12-22 06:45:52 +11:00
Willyb321
94141aa3c5 mining tools in their own section, prep for scl 2018-12-19 08:21:13 +11:00
Willyb321
aca90d7077 prep for pulse wave analysers 2018-12-18 08:25:30 +11:00
felixlinker
a66fa8e83f Merge branch 'develop' 2018-12-05 20:32:55 +00:00
felixlinker
194db07057 Hotfix pip parsing in control code 2018-12-05 20:31:46 +00:00
William Blythe
307886d4ae Merge branch 'develop' 2018-12-04 12:02:02 +11:00
willyb321
bbba048129 update to bugsnag 5 2018-11-30 06:56:01 +11:00
willyb321
222173b388 add category select to orbis modal 2018-11-25 07:54:16 +11:00
willyb321
ec0d05e081 add beta resting heat 2018-11-22 07:31:45 +11:00
Willyb321
b3be0bd639 remove logs 2018-11-21 09:00:50 +11:00
Willyb321
529d80682c Merge branch 'develop' of github.com:edcd/coriolis into develop 2018-11-21 08:51:32 +11:00
Willyb321
934de01803 add announcements to actual page 2018-11-21 08:50:53 +11:00
William
3367580d78 Apply diminishing returns for hull resistance modding to all modules (#425)
References #396
2018-11-21 08:38:46 +11:00
felixlinker
fbf59219d0 Apply diminishing returns for hull resistance modding to all modules 2018-11-20 20:57:39 +00:00
William Blythe
77401a3b3f get ready for dw2 site 2018-11-19 09:21:18 +11:00
willyb321
7c587c29aa add build reporter plugin 2018-11-19 07:12:36 +11:00
willyb321
2295dccd82 re-enable bugsnag 2018-11-19 07:10:05 +11:00
willyb321
cc4ad6d132 and a bit moree 2018-11-19 06:58:53 +11:00
willyb321
8a2d27290a add some logging to assist DWE2 people (temp) 2018-11-19 06:58:12 +11:00
willyb321
eda61a8e06 in theory fix guardian pds
Closes #339
2018-11-17 09:26:57 +11:00
William Blythe
3987c4e681 maybe an actually good service worker for once 2018-11-13 09:23:29 +11:00
William Blythe
71b90eb6f4 maybe an actually good service worker for once 2018-11-13 09:11:43 +11:00
willyb321
4e891f382c prep for import 2018-11-13 06:25:17 +11:00
willyb321
3d3f9e44b5 add damage per shot 2018-11-11 01:57:06 +11:00
willyb321
dd7a133caa add search bar initial
why am i awake its like 2am
2018-11-11 01:31:30 +11:00
willyb321
ae247c4812 support beta flag 2018-11-10 09:39:22 +11:00
William Blythe
6c932f96a6 add fill weapons for AX weps 2018-11-09 11:28:37 +11:00
William Blythe
0ea25692d3 work on sw 2018-11-07 13:09:04 +11:00
William Blythe
e129e1da39 work on sw 2018-11-07 13:08:35 +11:00
William Blythe
8acd32b0fc Merge branch 'develop' 2018-11-05 11:59:25 +11:00
William Blythe
8e5dd9fb8d add iframe to allow posting builds 2018-11-05 11:51:50 +11:00
William Blythe
97ce2828e0 mamba prep 2018-11-02 09:57:48 +11:00
William Blythe
f8f99a5aaa Merge branch 'develop' 2018-11-01 10:12:02 +11:00
William Blythe
70cfa58896 remove halloween 2018-11-01 07:30:07 +11:00
William Blythe
6e79ced51e remove halloween 2018-11-01 07:28:54 +11:00
William Blythe
56571f9c1f halloween 2018-10-31 09:20:35 +11:00
William Blythe
0b10cac85c halloween 2018-10-31 09:20:05 +11:00
Stratos Gerakakis
34c04a6354 Fixed Typo on "Armour Tactics" (#414)
* fix sw not registered

* Fix/no adsense (#412)

* Fixed Type on "Armour Tactics"
2018-10-29 09:14:32 +11:00
William Blythe
4e337c4ca1 add pax tooltip to outfitting too 2018-10-26 09:28:33 +11:00
William Blythe
5048b7e094 add pax tooltip 2018-10-26 09:05:33 +11:00
William Blythe
27fbc1ad66 remove file 2018-10-25 10:36:35 +11:00
William Blythe
4ab376d9ed Fix/no adsense (#412) 2018-10-24 09:08:13 +11:00
William
dfffc3a268 Fix/no adsense (#412)
* No adsense

* remove script tag
2018-10-24 09:06:24 +11:00
William
b59fa15e00 Merge pull request #411 from EDCD/fix/chart-labels
Fix VerticalBarchart labels
2018-10-24 07:52:58 +11:00
felixlinker
12bca4c44e Fix VerticalBarchart labels 2018-10-23 21:48:07 +01:00
William Blythe
2858ef3e93 Merge branch 'develop' 2018-10-23 13:03:58 +11:00
William Blythe
7d99471f89 sw 2018-10-23 12:41:56 +11:00
William Blythe
a2ab708ac9 format 2018-10-23 12:14:28 +11:00
William Blythe
a34a9c355f fix sw not registered 2018-10-23 11:57:07 +11:00
William Blythe
3215b3942d fix sw not registered 2018-10-23 11:56:33 +11:00
William Blythe
557c0afd9b Merge branch 'master' into develop 2018-10-23 11:50:58 +11:00
William Blythe
d52365a204 /shrug 2018-10-23 09:55:42 +11:00
felixlinker
14b2a14e58 Merge branch 'master' into develop 2018-10-22 23:52:09 +01:00
William Blythe
7f24904f77 various bits 2018-10-23 09:46:50 +11:00
William Blythe
da07790594 various bits 2018-10-23 09:40:13 +11:00
William
5008c7cd74 Update sw.js 2018-10-23 08:15:54 +11:00
willyb321
a778b1b6e1 update 2018-10-22 17:15:09 +11:00
William Blythe
bd9771f9ba test run 2018-10-22 11:49:15 +11:00
William Blythe
600c244f9b test run 2018-10-22 11:44:14 +11:00
William Blythe
a599b1a076 remove d3 rollup that doesnt seem to serve a purpose 2018-10-22 09:21:33 +11:00
willyb321
3e0a5e22b1 Merge branch 'develop' 2018-10-21 10:21:04 +11:00
willyb321
3a6ac818c2 reload anyway 2018-10-21 10:17:53 +11:00
willyb321
6f077d4c41 use register-service-worker 2018-10-21 10:16:31 +11:00
willyb321
9c767c928c fix icons and manifest
was not flattened
2018-10-21 10:03:01 +11:00
willyb321
515f4ad3da sw work 2018-10-21 09:50:15 +11:00
willyb321
4fcf074595 dont git reset in docker 2018-10-21 08:37:08 +11:00
willyb321
e5f8153a34 add announcements to menu 2018-10-21 08:27:53 +11:00
William
571854a11c Merge pull request #405 from EDCD/feature/code-quality
Cosmetical improvements
2018-10-20 11:00:58 +11:00
William
1f22f249a1 Merge pull request #407 from EDCD/feature/number-inputs
Enhance number editing
2018-10-20 11:00:47 +11:00
felixlinker
718ac0a514 Enhance number editing 2018-10-13 22:29:12 +01:00
willyb321
8f089cb1ee hopefully fix some sw bugs 2018-10-13 09:22:16 +11:00
felixlinker
d19a7276dd Optimized imports 2018-10-05 00:06:57 +01:00
felixlinker
10fffe67fc Code style fixes 2018-10-05 00:06:33 +01:00
William Blythe
f0bf8e8ce2 import babel polyfill 2018-10-03 14:17:56 +10:00
William
598cf8d677 Merge pull request #404 from EDCD/feature/fix-imports
Fix journal import
2018-10-02 10:14:02 +10:00
felixlinker
90f03de3fe Fix journal import 2018-10-01 22:24:18 +01:00
William
e0766f4424 Merge pull request #400 from EDCD/feature/fixes
Various fixes
2018-09-30 06:21:06 +10:00
felixlinker
28a90768e4 Start searching for available slots at 0 because the Type9 starts there 2018-09-29 20:26:40 +01:00
felixlinker
f3d917ccbe Fixed trailing zero check in journal style import 2018-09-29 20:04:40 +01:00
felixlinker
7e5d52385d Fixed loops in journal style import 2018-09-29 20:03:58 +01:00
felixlinker
4368015dc0 Fixed variable assignement that should be a comparison 2018-09-29 17:45:45 +01:00
felixlinker
1201da1811 Differentiate between uneditable and un-highlighted properties in the modifications menu 2018-09-29 16:40:36 +01:00
felixlinker
d195b568b0 eslint indentation fixes 2018-09-29 01:03:00 +01:00
felixlinker
c9866c146b Stop dividing burst two times by 100 2018-09-29 00:55:37 +01:00
felixlinker
5d52809d0d Catch undefined values in ship build parsing 2018-09-28 23:28:46 +01:00
willyb321
8f0cca4fd9 fix 2018-09-29 08:06:15 +10:00
willyb321
e46bb425fe minimize in prod 2018-09-29 07:50:36 +10:00
willyb321
06dc110025 migrate to babel 7, webpack 4, dep updates, lots 2018-09-29 07:49:04 +10:00
felixlinker
e9c34c636a Don't filter values but map falsy ones to zero 2018-09-28 22:35:07 +01:00
felixlinker
59d38cbd33 use react-extras instead of auto-bind 2018-09-26 23:15:09 +01:00
William
51f5188efc Merge pull request #393 from EDCD/feature/mc-pips
Add multicrew pips
2018-09-25 07:15:44 +10:00
felixlinker
be8934da80 Added multi crew pips 2018-09-23 22:30:05 +01:00
felixlinker
18d78b3089 Added auto-bind dependency 2018-09-23 22:29:45 +01:00
willyb321
b1ff4e84f7 Merge branch 'master' into develop 2018-09-23 11:42:45 +10:00
willyb321
bed2ede701 Merge branch 'release/3.0.0' 2018-09-23 11:42:44 +10:00
willyb321
124bd62d2c 3.0.0 2018-09-23 11:42:38 +10:00
willyb321
975846f4ab Merge branch 'master' into develop 2018-09-23 11:41:23 +10:00
willyb321
3f73f9be10 Merge branch 'release/2.9.18' 2018-09-23 11:41:22 +10:00
willyb321
d218101708 2.9.18 2018-09-23 11:41:16 +10:00
Felix Linker
608ce12156 Undefined variable reference fix 2018-09-17 00:21:13 +02:00
William
80d653483a Merge pull request #386 from EDCD/feature/absolute-mods
Absolute modding
2018-09-17 07:30:44 +10:00
Felix Linker
f9a1e5afd9 Merge branch 'develop' into feature/absolute-mods 2018-09-16 23:29:59 +02:00
Felix Linker
7f5181a9c7 Improved modification menu design 2018-09-15 01:54:17 +02:00
Felix Linker
93d8f15f26 Moved diminishing returns for resistance modding on hrp into getModValue 2018-09-15 01:32:41 +02:00
Felix Linker
06d8930777 Typos fixed 2018-09-15 01:22:22 +02:00
William
23f4024e09 Merge pull request #385 from EDCD/feature/fixes
Fix experimental effect reverse engineering for resistance modding
2018-09-15 09:14:39 +10:00
Felix Linker
a5e9a71037 Clamp modification values to -50k and 50k 2018-09-15 01:12:39 +02:00
willyb321
423d42bc8a remove package-lock 2018-09-15 08:56:08 +10:00
willyb321
4075bab3d0 whoops 2018-09-15 08:54:31 +10:00
willyb321
469f7a0a48 format and request permission to store data
fixes people losing builds
2018-09-15 08:54:06 +10:00
Felix Linker
25d9f8ec61 Fix experimental effect reverse engineering for resistance modding 2018-09-15 00:50:14 +02:00
Felix Linker
1151bd1614 optmul changes are displayed additively 2018-09-15 00:42:39 +02:00
Felix Linker
2456ce330b Change module modding to have absolute values entered 2018-09-15 00:40:49 +02:00
Felix Linker
f86ce62c27 Optimized statistics calculation in shipyard/Ship 2018-09-15 00:38:38 +02:00
Felix Linker
c295a9f4e4 Added percentage as unit 2018-09-15 00:37:50 +02:00
Felix Linker
c4186faa4a Allow to set an absolute value for a module 2018-09-15 00:31:00 +02:00
William
00f4889c93 Merge pull request #381 from EDCD/feature/stats
Modules return null instead of undefined when a value is not given
2018-09-14 09:10:53 +10:00
Felix Linker
b6c83f4aba fix: hullboost mod imports 2018-09-13 23:42:51 +02:00
Felix Linker
3fa040c210 Modules return null instead of undefined when a value is not given 2018-09-13 23:02:35 +02:00
Felix Linker
8c4957c21e Merge pull request #364 from EDCD/feature/stats
Show absolute values next to modification values
2018-09-13 00:56:08 +02:00
Felix Linker
a178c59aa3 Merge branch 'develop' into feature/stats 2018-09-13 00:54:35 +02:00
William
0d9fcd97d7 Merge pull request #377 from EDCD/feature/please-willy
wait for load event before registering service worker
2018-09-12 08:40:39 +10:00
Felix Linker
b02ca117be wait for load event before registering service worker 2018-09-12 00:38:13 +02:00
William
ca2e2a22dd Merge pull request #371 from EDCD/feature/fixes
Various fixes
2018-09-12 08:17:48 +10:00
William
035e217124 Merge pull request #370 from pavchip/feature/update-russian-localization
Update Russian localization
2018-09-12 08:12:47 +10:00
Felix Linker
6424ab283a maxmass is not modified for shield generators now 2018-09-11 21:27:38 +02:00
Felix Linker
f30f10c904 hullboost modified values is calculated like shieldboost now 2018-09-11 20:44:23 +02:00
Felix Linker
8ce09a607f Implemented diminishing returns for hrp resistance modding 2018-09-11 13:09:29 +02:00
Pavel Chistyakov
5b48e30798 Update Russian localization 2018-09-11 13:31:11 +03:00
Felix Linker
0d7e6af2e6 Boost time now considers modifications 2018-09-10 01:03:13 +02:00
Felix Linker
be3ca23aee Consider guardian module reinforcement packages when calculation module protection 2018-09-10 01:00:36 +02:00
Felix Linker
28f464ba5a fix: falloff scaling now only applies for focused modification 2018-09-10 00:45:46 +02:00
Felix Linker
8cedd1b8bc When formating a value for a Module assign it to zero if it is undefined 2018-09-09 17:20:21 +02:00
Felix Linker
56cbc49d04 fix: when querying falloff mods is checked to be not undefined 2018-09-09 16:26:03 +02:00
Felix Linker
5d1ccd2a20 Removed synthetic values from modifications menu 2018-09-09 16:07:01 +02:00
Felix Linker
f898fbc55e Optimized _getModifiedValue 2018-09-09 16:01:54 +02:00
Felix Linker
63acad3aeb Sort mods in modifications menu by editable first 2018-09-09 16:01:54 +02:00
Felix Linker
5b4b0b8dff range and ranget are rounded with fixed decimal places now 2018-09-09 16:01:54 +02:00
Felix Linker
8492dd74f9 falloff and range are formated rounded to two decimal places 2018-09-09 16:01:54 +02:00
Felix Linker
77e652cd34 Allow for SE prefixes when formating module stats 2018-09-09 16:01:53 +02:00
felixlinker
5069d7e464 Modifications menu shows absolute stat values 2018-09-08 11:39:28 +02:00
felixlinker
8b3e9c0f63 Added method to retrieve a formatted stat from a module 2018-09-08 11:34:33 +02:00
felixlinker
99e76e480b Introduced translation for fallofffromrange 2018-09-08 11:33:26 +02:00
felixlinker
c161ed2298 Added units meters and angle 2018-09-08 11:32:56 +02:00
William Blythe
464770f096 Merge branch 'develop' 2018-09-06 09:57:34 +10:00
felixlinker
9fba1be814 Allow to retrieve unmodded vals from module 2018-09-05 17:58:16 +02:00
William
b419b8b104 Merge pull request #356 from EDCD/feature/caustic
Label fix
2018-09-01 09:12:20 +10:00
William
0cae955ca2 Merge pull request #359 from EDCD/feature/measure-charts
Fixed resize observer loop limit exceeded
2018-09-01 08:51:33 +10:00
felixlinker
ec70ad5d29 WeaponDamageChart wrapped in div instead of span 2018-08-31 15:02:44 +02:00
felixlinker
8a386c4ece Use react-container-dimensions instead of react-measure 2018-08-31 15:02:17 +02:00
felixlinker
013460ada4 Ship summary table header fix 2018-08-30 19:14:16 +02:00
William Blythe
d46ad89dc5 fix sw waiting 2018-08-30 08:25:50 +10:00
William
86c53c8e46 Merge pull request #354 from EDCD/feature/caustic
Bug fixes introduced with caustic damage calculation
2018-08-30 08:20:01 +10:00
felixlinker
df14786e79 Fixed bulkheads value for caustic damage 2018-08-29 11:37:28 +02:00
felixlinker
24f206ad82 Fixed ship summary table width 2018-08-29 11:37:14 +02:00
William Blythe
242c3efe45 Merge branch 'develop' of github.com:edcd/coriolis into develop 2018-08-29 09:24:27 +10:00
William
07a848b906 Merge pull request #353 from EDCD/feature/caustic
Added support for armour caustic resistances
2018-08-29 09:24:20 +10:00
felixlinker
1b5402fd2d Added support for caustic armour resistance 2018-08-28 12:38:47 +02:00
felixlinker
903d791549 Fixed tooltip for module protection in ShipSummaryTable 2018-08-28 10:53:40 +02:00
William Blythe
f03c164f1c Merge branch 'feature/alliance-crusader' into develop 2018-08-28 14:15:08 +10:00
William Blythe
107bab0192 Merge branch 'develop' 2018-08-27 08:42:21 +10:00
William Blythe
41f5ebb2f1 Merge remote-tracking branch 'origin/master' 2018-08-27 08:41:35 +10:00
William
266090dc2a Update sw.js 2018-08-27 08:34:10 +10:00
William
83f29f3d62 Merge pull request #351 from EDCD/feature/modules
Added support for research-limpet-controller
2018-08-27 08:30:43 +10:00
Felix Linker
3e6137ca96 Merge branch 'develop' into feature/modules 2018-08-26 20:13:33 +02:00
Felix Linker
58510bbd22 Added support for research-limpet-controller 2018-08-26 20:12:00 +02:00
William Blythe
2f7be75bcf update orbis integration 2018-08-26 09:48:12 +10:00
willyb321
0aecbbf892 [test] switch to service workers
dont merge to live until can be confirmed to work well
2018-08-26 09:44:44 +10:00
William Blythe
9ef2f4179b add alliance crusader 2018-08-25 18:09:58 +10:00
William Blythe
afbfe3ea12 update orbis integration 2018-08-25 13:59:14 +10:00
William Blythe
b145a2da85 update orbis integration 2018-08-25 13:58:55 +10:00
William
a679f7dd98 Merge pull request #348 from EDCD/feature/modules
Added support for new modules
2018-08-24 08:55:28 +10:00
William
f343366ea1 Merge pull request #350 from EDCD/feature/offence-table
Feature/offence table
2018-08-24 08:55:05 +10:00
Felix Linker
1bbea7dda0 Improved docs 2018-08-23 18:21:00 +02:00
Felix Linker
da097e0955 Added piechart for overall damage in offence tab 2018-08-23 18:09:18 +02:00
Felix Linker
1307474755 Added tooltips to offence table summary 2018-08-23 18:08:50 +02:00
Felix Linker
80f5a95297 Added support for new modules 2018-08-23 02:30:39 +02:00
William
e619966679 Merge pull request #346 from EDCD/feature/defence-proportions
Shield defence proportions calculated correctly
2018-08-22 07:04:13 +10:00
William
3f94382925 Merge pull request #345 from EDCD/feature/offence-table
Offence table improved (+Bug fix)
2018-08-22 07:03:57 +10:00
Felix Linker
ea7a6f8872 Added object documentation to shield damage multiplier objects 2018-08-20 03:45:05 +02:00
Felix Linker
4e87b3a0b8 Fixed shield proportion calc in defence panel 2018-08-20 03:44:24 +02:00
Felix Linker
8463dd46f7 Removed unused variable 2018-08-20 03:43:38 +02:00
Felix Linker
3febe465f6 reinforcement/booster contribution to defences now calulcated as multiplier 2018-08-20 02:13:11 +02:00
Felix Linker
45b834c424 Added summary and overall sdps to offence table 2018-08-20 02:12:06 +02:00
William
1ca041097b Merge pull request #343 from felixlinker/summary-shield-regen
Ship summary correctly calculates shield recovery/recharge time
2018-08-20 07:33:04 +10:00
William
587f7acd5b Merge pull request #344 from felixlinker/doc-fixes
doc fixes
2018-08-20 07:32:53 +10:00
Felix Linker
9e64e4a26b doc fixes 2018-08-19 23:26:17 +02:00
Felix Linker
96e09ab36c Ship summary correctly calculates shield recovery/recharge time 2018-08-19 15:41:27 +02:00
willyb321
4268d3f07b Merge branch 'develop' 2018-08-19 08:47:40 +10:00
William
1767a2aed5 Merge pull request #342 from felixlinker/import-shieldboost
Shieldboosters imported correctly now
2018-08-19 07:00:55 +10:00
William
9f061506bb Merge pull request #341 from felixlinker/shotspeed-blueprints
No special treatment for shotspeed modifications anymore
2018-08-19 07:00:19 +10:00
willyb321
0eb9a2048d Fix dev build
Closes #340
2018-08-19 06:59:28 +10:00
Felix Linker
faeafb2402 Shieldboosters imported correctly now 2018-08-18 22:28:13 +02:00
Felix Linker
2a0acfa6cb No special treatment for shotspeed modifications anymore 2018-08-18 21:15:37 +02:00
willyb321
0ac44ac267 (hopefully) fix bugsnag crashing the build 2018-08-17 08:05:04 +10:00
willyb321
b9ae3a4d5a just delete builds that are invalid when importing
temp fix until i figure out a better way
2018-08-17 07:49:49 +10:00
willyb321
b1aefb0003 just delete builds that are invalid when importing
temp fix until i figure out a better way
2018-08-17 07:48:48 +10:00
willyb321
f6a41ec55c make orbis auth checker better 2018-08-10 08:10:38 +10:00
willyb321
39650cc584 add orbis auth status 2018-08-09 16:43:21 +10:00
willyb321
d837287da7 add link to migrate to official domains 2018-08-07 11:46:18 +10:00
willyb321
6e718a39d1 add link to migrate to official domains 2018-08-07 11:46:01 +10:00
willyb321
2e7db02238 fix url 2018-08-07 11:30:23 +10:00
willyb321
dbfd108819 upload all builds to orbis under settings 2018-08-07 11:29:42 +10:00
willyb321
82c0f6fc0f hopefully fix beta.coriolis.edcd.io 2018-08-07 07:47:47 +10:00
willyb321
9e6bbaa67d fix url 2018-08-05 11:55:02 +10:00
willyb321
012a203c4a fix url 2018-08-05 11:54:51 +10:00
willyb321
c7ba129ed7 use different url shortener 2018-08-05 11:52:53 +10:00
willyb321
fbe57d00db use different url shortener 2018-08-05 11:41:53 +10:00
willyb321
dc4fdf215c fix compose again 2018-08-05 11:35:06 +10:00
willyb321
2166ac1584 fix docker-compose file 2018-08-05 10:41:41 +10:00
willyb321
504756de09 update orbis login 2018-08-02 12:56:45 +10:00
willyb321
90f04dba94 add link to orbis data 2018-08-01 16:35:44 +10:00
willyb321
bfd7645fb7 fix localhost uri 2018-08-01 14:27:09 +10:00
willyb321
c37c6983c3 orbis auth 2018-08-01 14:14:30 +10:00
willyb321
736c700aa0 orbis trial 2018-07-31 15:13:25 +10:00
willyb321
de1cb901fc more WIP for orbis 2018-07-28 11:36:44 +10:00
willyb321
3d9d6397f6 fix url shortener 2018-07-28 07:09:16 +10:00
willyb321
dbe836729f WIP for orbis 2018-07-27 11:44:14 +10:00
William Blythe
82fa93e676 Merge branch 'develop' into 'master' 2018-07-26 11:31:08 +10:00
willyb321
90a3392b80 oops i forgot a thing 2018-07-26 11:11:45 +10:00
willyb321
b7f715bd5e fix dockerfile to copy the right config 2018-07-26 08:58:27 +10:00
willyb321
5f88f46770 oops, remove ports and fix branch 2018-07-26 08:39:32 +10:00
willyb321
2244c91a64 Merge remote-tracking branch 'origin/develop' into develop 2018-07-26 08:33:54 +10:00
willyb321
78134404c3 prettify nginx, dockerfile updates 2018-07-26 08:33:05 +10:00
William
c9cd6b175d Merge pull request #333 from felixlinker/autoloader-sdps
SDPS takes autoloader into account
2018-07-26 08:06:09 +10:00
willyb321
5f70d283e0 do reset to branch 2018-07-26 07:25:32 +10:00
willyb321
cd1e27fd11 fix nginx conf 2018-07-25 11:48:01 +10:00
willyb321
0fd1c19514 dont reset 2018-07-25 11:25:21 +10:00
willyb321
a82dffd77d initial docker 2018-07-25 11:22:58 +10:00
Felix Linker
d3eed87077 new module method getSDps that takes autoloader into account 2018-07-25 02:07:53 +02:00
willyb321
d99f8ad7e7 remove preinstall script 2018-07-25 10:05:23 +10:00
William
e933305407 Merge pull request #329 from felixlinker/resistance-stacking
Caps for resistances introduced
2018-07-22 07:36:46 +10:00
felixlinker
690bc5a64a Resistance special effects now applied correctly 2018-07-20 17:54:07 +02:00
felixlinker
d2380a5c9c Armour resistances caps introduced 2018-07-20 14:11:33 +02:00
felixlinker
c2d0a6e9e0 Removed duplicate code on armour calculations 2018-07-20 14:11:33 +02:00
Felix Linker
a8c44fddca Shield resistances caps introduced 2018-07-20 14:11:33 +02:00
willyb321
0e6b306bdc Merge branch 'develop' 2018-07-20 07:31:29 +10:00
William
9b2a47ba0c Merge pull request #327 from felixlinker/sb-fixes
Fixes for shieldboost mods and special effects on shield booster
2018-07-20 07:29:49 +10:00
Felix Linker
3038ebc1c7 sb mod displays percentage change relative to 1+boost; sb special effect applied correctly 2018-07-19 17:37:34 +02:00
willyb321
5a48d5b400 Merge branch 'develop' 2018-07-19 10:26:21 +10:00
willyb321
f36edfe98d Fix guardian HRPs not calculating
Fixes #325
2018-07-19 10:22:42 +10:00
willyb321
3e80d6e13b Merge branch 'develop' 2018-07-18 08:30:07 +10:00
willyb321
c8f0a1ccc6 Fix #137
Recalculate stats after drag and drop
2018-07-18 08:29:08 +10:00
William
79325a3129 Merge pull request #319 from felixlinker/blueprint-special-resets
Resetting special effects for blueprints now re-calculates all stats
2018-07-18 08:04:39 +10:00
felixlinker
1ca0fffe5e Resetting special effects for blueprints now re-calculates all stats 2018-07-17 18:21:10 +02:00
willyb321
f5b9842b2e Merge branch 'develop' 2018-07-14 10:54:46 +10:00
willyb321
72ea3f125e guardian shard cannon 2018-07-14 10:52:28 +10:00
willyb321
f7a2408e44 add shock cannon 2018-07-14 10:32:47 +10:00
willyb321
c05de9a085 Merge branch 'develop' 2018-07-14 10:10:45 +10:00
willyb321
fc657f3b2d add guardian mrps and hrps 2018-07-14 10:09:45 +10:00
willyb321
e388de0364 Merge branch 'develop' 2018-07-11 12:02:30 +10:00
willyb321
26e55afb29 fix #315 2018-07-10 14:38:29 +10:00
willyb321
9b8e1039f6 also hide / disable buttons on firefox 2018-07-09 06:51:15 +10:00
willyb321
082d14ba50 tell user sending to edengineer doesnt work with firefox 2018-07-09 06:44:54 +10:00
willyb321
87ead595c6 possible fix for shopping list 2018-07-06 07:28:23 +10:00
willyb321
fb073acdc3 fix send to edengineer button disabled for some people 2018-07-06 07:09:05 +10:00
willyb321
3dda4e24bf update readme 2018-07-04 08:43:18 +10:00
willyb321
c966bd08ed preinstall clone coriolis-data 2018-07-04 08:37:11 +10:00
willyb321
692c73a6d0 add esdoc 2018-07-04 07:28:39 +10:00
willyb321
849b484b4d register specials in shopping list
ref msarilar/EDEngineer#431
2018-07-03 08:27:12 +10:00
William Blythe
9489baccda update .gitignore 2018-07-02 10:34:24 +10:00
willyb321
62cac15222 a couple fixes on /import 2018-07-02 06:41:01 +10:00
willyb321
0ee9ce2958 fix more args 2018-07-01 13:41:23 +10:00
willyb321
a83007fc6c fix parameter in the wrong function 2018-07-01 13:34:29 +10:00
willyb321
901f266dad dont allow stacking guardian fsd boosters 2018-07-01 13:31:59 +10:00
willyb321
6009426f04 add logic to handle guardian fsd boosters 2018-07-01 12:06:12 +10:00
willyb321
6fb89df4cd fix gsrp not updating shield strength 2018-07-01 11:44:53 +10:00
willyb321
48b99425a3 fix guardian shield reinforcement packages
add shield addition logic
2018-07-01 11:18:20 +10:00
willyb321
9569f9b09b more work on fixing resistances
hopefully getting close
2018-07-01 08:56:07 +10:00
willyb321
7421896902 store selected commander and also the list of commanders 2018-07-01 08:14:55 +10:00
willyb321
b837c8d66c add a cmdr dropdown instead of typing it manually 2018-06-30 11:11:08 +10:00
willyb321
e180f96eab hopefully much more accurate resistance calculation 2018-06-29 10:16:55 +10:00
willyb321
de1f1f790e gsrp 2018-06-29 07:39:59 +10:00
willyb321
fefa74ce0f add krait and challenger to /import 2018-06-29 07:39:37 +10:00
willyb321
a2ceb70aa4 simplify calcPipSpeed
also fix the order of parameters
2018-06-28 06:52:23 +10:00
willyb321
634103f144 update eslintrc to allow es6 2018-06-28 06:48:56 +10:00
willyb321
f3379de81e add formula to calculate pip speed
not used yet, but will be used eventually
2018-06-28 06:48:37 +10:00
willyb321
064ee74c1a fix jsdoc typo 2018-06-28 06:48:16 +10:00
willyb321
0e0c15fe3b lint a tad 2018-06-27 09:11:12 +10:00
willyb321
3c855c608d Merge branch 'develop' 2018-06-27 07:47:44 +10:00
willyb321
414516289c add message if sending to edengineer failed 2018-06-27 07:47:22 +10:00
willyb321
d79fa71337 Merge branch 'develop' 2018-06-27 05:55:18 +10:00
willyb321
39c2ca94c8 add "send to edengineer" button 2018-06-27 05:54:52 +10:00
willyb321
acf1c5ce04 Merge remote-tracking branch 'origin/develop' into develop 2018-06-26 08:02:58 +10:00
willyb321
fd7923155f more prep for gpd 2018-06-26 08:02:18 +10:00
willyb321
2aa6a6daa2 translations update 2018-06-26 08:01:57 +10:00
willyb321
9662621980 fix name of guardian pd 2018-06-26 08:01:46 +10:00
willyb321
37d007d9ab apply module blueprint and special before setting value
Fixes #297
2018-06-26 07:53:14 +10:00
willyb321
a4e48c359a Merge branch 'develop' 2018-06-17 08:24:58 +10:00
willyb321
8ef0101a6e apply module blueprint and special before setting value
Fixes #297
2018-06-17 08:24:24 +10:00
willyb321
e21f109026 Merge branch 'develop' 2018-06-15 10:58:19 +10:00
willyb321
737837eebd Merge remote-tracking branch 'origin/develop' into develop 2018-06-15 10:57:56 +10:00
willyb321
ba09b54409 fix jsdoc 2018-06-15 10:57:48 +10:00
willyb321
417091c648 fix lint 2018-06-15 10:45:00 +10:00
William Blythe
5a02368298 Merge branch 'develop' 2018-06-14 13:02:47 +10:00
William Blythe
da7c739497 add paypal button 2018-06-14 13:01:37 +10:00
willyb321
0842281466 Merge branch 'release/2.9.17' into develop 2018-06-14 07:08:21 +10:00
willyb321
b480f879b1 Merge branch 'release/2.9.17' 2018-06-14 07:08:21 +10:00
willyb321
f8a215d790 2.9.17 2018-06-14 07:08:17 +10:00
willyb321
da69f3b2c8 New URL Shortener
eddp.co shortened links are considered End-Of-Life from now on
2018-06-14 07:00:05 +10:00
willyb321
fc442c1a42 Merge branch 'master' into develop 2018-06-14 06:50:46 +10:00
willyb321
cf59a6b9fd whoops 2018-06-14 06:50:08 +10:00
willyb321
34afcd511a Merge branch 'develop' 2018-06-14 06:44:13 +10:00
willyb321
e2444a2e4e track ship in ga 2018-06-14 06:43:54 +10:00
willyb321
692516de9b Merge branch 'develop' 2018-06-13 08:37:31 +10:00
willyb321
d51009c823 add time between boosts
Fixes #269
2018-06-13 08:36:56 +10:00
willyb321
2f121bef5e Merge branch 'develop' 2018-06-13 06:46:27 +10:00
willyb321
61f7d376d2 store number of rolls per grade 2018-06-13 06:46:12 +10:00
willyb321
9e253012e6 Merge branch 'develop' 2018-06-13 06:23:57 +10:00
willyb321
e4e5b1327b fix rank link showing on things that dont need a rank link 2018-06-13 06:22:45 +10:00
willyb321
6ef3b227b8 Merge branch 'develop' 2018-06-13 06:16:37 +10:00
willyb321
bf657a0945 fix view changes 2018-06-13 06:16:21 +10:00
willyb321
c3dd1886c9 Merge branch 'develop' 2018-06-12 12:12:29 +10:00
willyb321
32498bb8a7 properly fix super cap 2018-06-12 11:32:37 +10:00
willyb321
69bb90a0e4 Merge branch 'release/2.9.16' 2018-06-12 11:03:18 +10:00
willyb321
8e2988edf0 Merge branch 'release/2.9.16' into develop 2018-06-12 11:03:18 +10:00
willyb321
876a352cfd 2.9.16 2018-06-12 11:03:13 +10:00
willyb321
84e44cabfa mats list modal allow setting # of rolls 2018-06-12 09:49:07 +10:00
willyb321
36a838d565 add material shopping list 2018-06-12 09:09:05 +10:00
willyb321
9ee8693f40 add material icon 2018-06-12 09:08:44 +10:00
willyb321
6f02965149 Merge branch 'develop' 2018-06-12 07:56:19 +10:00
willyb321
27ce82de3b switch to google analytics from piwik 2018-06-12 07:56:01 +10:00
Pat Nellesen
3d5a9ef220 Cleanup for linting issues, and added last bit of focus handling for … (#304)
* Cleanup for linting issues, and added last bit of focus handling for selected modifications and specials

* added correct value for selectedRefId for utility slot section menu when Empty All selected
2018-06-11 08:23:03 +10:00
Pat Nellesen
9b81f6efd2 Feature/#293 header keynav (#303)
Added keydown and focus handlers for Slot Section Menus ("Core Internal", "Optional Internal", etc.)

When focus is on the header, Enter key will open the menu and set focus to either the first option, or else the currently selected option, such as "Planetary Explorer" in Core Internal menu (if one has been previously selected).

While menu is open, Tab and Shift-Tab will move the focus up and down as expected. Shift-tab on first option will move focus to last option in the menu, and Tab on the last option will move focus to the top. Focus will stay inside the menu until menu is closed.

When focus is on a menu options, hitting the Enter key will trigger the onClick function for that option, and will set the option as the currently selected option for that menu.

Esc key will close the menu and set focus to the menu header H1 element.
2018-06-10 09:00:43 +10:00
willyb321
3e77e23d71 Merge remote-tracking branch 'origin/develop' into develop 2018-06-10 06:26:17 +10:00
willyb321
120c032c82 Fix approximately a lot of lint issues 2018-06-10 06:24:55 +10:00
William
46e15b8ecd Merge pull request #301 from BenJuan26/feature/linting
Fix obvious lint issues
2018-06-08 07:15:39 +10:00
William
d71d87041b Merge pull request #300 from BenJuan26/feature/travis-install-coriolis-data
Clone coriolis-data into Travis before npm install
2018-06-08 07:14:54 +10:00
Benjamin Schubert
124bd58b9f Fix obvious linting issues 2018-06-07 10:50:52 -04:00
Benjamin Schubert
257b9b0562 Clone coriolis-data into Travis before npm install 2018-06-07 10:00:46 -04:00
willyb321
b8e15f691d Merge branch 'develop' 2018-06-07 06:57:33 +10:00
willyb321
2255e3bfc4 Temp fix for #273
Don't allow engineering on guardian PP
2018-06-07 06:57:00 +10:00
willyb321
8797d84605 Merge branch 'develop' 2018-06-06 06:27:18 +10:00
willyb321
719179a923 Add link to ranks page
Fixes #298
2018-06-06 06:26:02 +10:00
willyb321
1d544099f6 Merge branch 'release/2.9.15' 2018-05-30 07:43:16 +10:00
willyb321
9b131a762a Merge branch 'release/2.9.15' into develop 2018-05-30 07:43:15 +10:00
willyb321
08c5d2256a 2.9.15 2018-05-30 07:43:09 +10:00
willyb321
ed6ee4341f Fix crash when applying blueprint
thanks bugsnag
2018-05-30 07:42:33 +10:00
willyb321
157c1148fb Implement temporary fix for #280 2018-05-30 07:37:20 +10:00
Pat Nellesen
507ea9e09e Fix to account for cases where there is only one module available for… (#291)
* Fix to account for cases where there is only one module available for a given slot (e.g. Sidewinder fuel tank)

* Fix for issue #289 - set focus to active module section if the slot has an active module

* Added activeSlot ref to set focus to current module when module list is opened

* Comment/console cleanup for PR #291
2018-05-30 07:25:35 +10:00
willyb321
af68cba7be add "view release changes" button below the update available banner 2018-05-29 07:31:58 +10:00
willyb321
224fbe0e8f add a "commits since last release button"
makes it easier for people to see activity
2018-05-29 07:23:20 +10:00
willyb321
49a6c5f2c4 Merge branch 'release/2.9.14' 2018-05-29 07:03:18 +10:00
willyb321
07c936897c Merge branch 'release/2.9.14' into develop 2018-05-29 07:03:18 +10:00
willyb321
3786fb907c 2.9.14 2018-05-29 07:03:11 +10:00
Pat Nellesen
752d9f0c68 Feature/#271 keyboard nav (#288)
* Initial stab at Tab/Enter key handlers for Slots - Added tabIndex="0" attribute for the Slot sections and for the <li> elements inside.

* Set up refs for slot <li> elements to allow focus manipulation.

* Added initial keyDown handlers for Enter and Tab keys.

* Additional update for focus handling inside slot selection <ul>

* Added Tab/Enter/Shift-Tab key handlers for Outfitting section. Needs some fine-tuning but is usable in current state

* Added keydown handler for Empty module selection. ToDo: handlers for engineering sections

* Cleanup

* Update to add focus on Modifications icon and Enter key handler to open modifications menu

* Updates to add keydown handlers for modifications menu, and tab/shift-tab/enter key handlers while mod menus are open.

* Additional updates for keyDown handlers

* Update to add tabindex=0 and Enter Key handling for specials menus. ToDo: keep focus inside specials menu until item is selected with Enter key.

* Further updates for keyDown handler in Modifications menus.

* Added keyDown handlers to full mod menu (when mod menu is opened and a mod has been previously selected)

* Update to add shift-tab and tab focus handling for number editor fields

* Additional tab/shift-tab handlers. Fixed bug with exp. effects being recaculated for every onBlur event.

* Added check to bypass focus reset if change in component was due to change in modification value.

* Additional updates to tab/shift-tab handlers for modifications menu.

* more updates

* "Final" updates and bug fixes - added code for cases where there were no specials.

* Final updates to set focus to appropriate element when slot/mod menus are closed.
2018-05-29 07:00:22 +10:00
Pat Nellesen
baf59aafcb Feature/#248 slider keyboard (#257)
* Added tabIndex=0 to <svg> in Slider to allow keyboard focus

* further tests for mobile keyboard fix

* Proof of concept of way to get mobile keyboard to open for Slider component

* Added CSS to slider input box to make it invisible but still usable

* Update to hide text input and move it up to same position as slider

* Change slider text field type to 'tel" to force numeric-only keyboard on mobile

* Added focus/blur handlers and initial styling to show/hide text field component

* Update to insure text field values are updated when slider moves, and vice versa.

* Added timeout handler for Outfitting sliders to raise keyboard after 1500ms. Doesn't work 100% on iPhone.

* Changed touch/mouse down event handlers to change the display value on the parent div, then use componentDidUpdate to set focus on the text box.

* Added tap/hold mousedown/hold keyboard display - works on desktop, iOS, and Android
2018-05-04 09:01:55 +10:00
willyb321
8787303d2a add a column with what type of shield, update tooltips too 2018-05-04 08:59:57 +10:00
willyb321
298eaa8b4b Merge remote-tracking branch 'origin/develop' 2018-05-03 10:58:46 +10:00
William Blythe
118c80af27 Fix kinetic and thermal res swapped
Closes #265
2018-05-03 10:54:55 +10:00
willyb321
7b87038a8c Merge branch 'develop' 2018-05-03 07:39:08 +10:00
willyb321
d103939e45 add experimentals to core internals 2018-05-03 07:38:54 +10:00
willyb321
fdc9171c69 Merge branch 'release/2.9.13' 2018-05-03 07:14:53 +10:00
willyb321
2a6ade3cab Merge branch 'release/2.9.13' into develop 2018-05-03 07:14:52 +10:00
willyb321
c8c42689f9 2.9.13 2018-05-03 07:14:45 +10:00
willyb321
da2f472f4d hopefully fix #262 2018-05-01 07:13:58 +10:00
William Blythe
3ba878237b fix super cap, nan with no shield 2018-04-30 10:57:42 +10:00
willyb321
7577fb53a2 biweaves are purple 2018-04-28 13:47:46 +10:00
willyb321
f2509f89ee change colour depending on shield 2018-04-28 13:39:17 +10:00
willyb321
9dd1f78330 make shield summary bar blue, change "damage from x" to "x hp" 2018-04-28 13:39:17 +10:00
willyb321
ebb6c2c420 more work on summary 2018-04-28 13:39:17 +10:00
willyb321
9d23dc1763 use set pips to calc shield 2018-04-28 13:39:17 +10:00
willyb321
5fa3f8703e pass pips to summary table 2018-04-28 13:39:17 +10:00
willyb321
797885faea more wip table 2018-04-28 13:39:17 +10:00
willyb321
68e7e9f5b7 fix crash when removing shield 2018-04-28 13:39:17 +10:00
willyb321
d1d165ad51 WIP summary table 2018-04-28 13:39:17 +10:00
willyb321
f43bd100e6 Merge branch 'release/2.9.12' 2018-04-28 12:16:21 +10:00
willyb321
0ebb247666 Merge branch 'release/2.9.12' into develop 2018-04-28 12:16:21 +10:00
willyb321
aa73bc2809 2.9.12 2018-04-28 12:16:11 +10:00
willyb321
c3fcdb918f prep for recon limpet controllers 2018-04-28 12:12:38 +10:00
willyb321
5e3722bcfd hopefully fix a crash 2018-04-28 11:37:24 +10:00
willyb321
6a4fca2eb1 fix .editorconfig 2018-04-26 13:39:52 +10:00
willyb321
fbba0e3ea5 Merge branch 'release/2.9.11' 2018-04-26 09:21:01 +10:00
willyb321
6922cfd047 2.9.11 2018-04-26 09:19:57 +10:00
willyb321
d93fc1d2d0 just update everything when applying a special 2018-04-26 09:06:18 +10:00
willyb321
b15695128f Merge branch 'release/release/v2.9.10' 2018-04-25 20:00:47 +10:00
willyb321
48290b2e75 2.9.10 2018-04-25 20:00:36 +10:00
willyb321
b62abef618 2.9.9 2018-04-25 19:55:05 +10:00
willyb321
e0c0778d82 Lowercase in import 2018-04-25 18:45:24 +10:00
willyb321
b4a82ae7c2 fix crash with not having modifications key 2018-04-25 18:00:01 +10:00
willyb321
fc73102b30 Merge remote-tracking branch 'origin/develop' into develop 2018-04-25 15:07:23 +10:00
willyb321
b14e7473f3 add guardian powerplant, gauss cannon and also plasma charger 2018-04-25 15:07:13 +10:00
willyb321
f4cc9fc722 fix typo 2018-04-25 14:20:45 +10:00
William
8d19ef7783 Merge pull request #256 from ExitCode/develop
Fixed bp preset highlight when specials alter on the same values
2018-04-24 14:49:39 +10:00
willyb321
32be186ec5 remove unused var 2018-04-23 18:15:44 +10:00
willyb321
abb0c7f90d remove debug logs 2018-04-23 18:15:24 +10:00
willyb321
71ddbdfe75 add specials tooltip 2018-04-23 14:51:45 +10:00
ExitCode
38463ad9a6 Fixed bp preset highlight when specials alter on the same values 2018-04-23 01:16:13 +02:00
willyb321
0870b90443 Merge branch 'release/2.9.8' 2018-04-23 07:38:34 +10:00
willyb321
70375f94c8 bump 2018-04-23 07:38:24 +10:00
William
2d4336116a Merge pull request #254 from ExitCode/develop
Value highlights for bp presets
2018-04-23 07:36:12 +10:00
willyb321
f52880765e fix legacy mods showing up 2018-04-23 07:34:52 +10:00
ExitCode
dbfe68decb Implemented active selected blueprint value highlight; updated look and feel 2018-04-22 05:36:19 +02:00
ExitCode
659f337de9 Added a spacer between the mod values 2018-04-22 05:34:25 +02:00
ExitCode
1d36d41da1 Tuned visibility of components in mod menu 2018-04-22 05:33:47 +02:00
ExitCode
0f90efaa54 Implemented a func to determine the current percentage level of a mod 2018-04-22 05:31:16 +02:00
willyb321
48ccab152b Merge branch 'release/2.9.7' 2018-04-22 12:27:20 +10:00
willyb321
4442930a82 bump 2018-04-22 12:26:06 +10:00
William
912a775088 Merge pull request #253 from ExitCode/develop
blue print workflow (easier editing of modules)
2018-04-22 12:08:01 +10:00
ExitCode
24a229d818 Implemented workflow on bp assignment; applied some styles 2018-04-22 03:43:57 +02:00
ExitCode
66afb61494 added new style for inline menu buttons 2018-04-22 03:41:21 +02:00
William
e7511cc05b Merge pull request #252 from ExitCode/develop
(optional) changed roll presets to 0-50-100
2018-04-22 09:09:37 +10:00
ExitCode
926f19a936 added default init 100% on bp selection as the new system makes it obvious to reach 100% 2018-04-22 00:59:44 +02:00
ExitCode
9e6f86b963 (optional) changed roll presets to 0-50-100 2018-04-22 00:28:04 +02:00
William
059de43a9a Merge pull request #251 from ExitCode/develop
reused random functionality as it worked better on percentage rolls between min max
2018-04-22 08:10:30 +10:00
ExitCode
e246b737b2 reused random functionality as it worked better on percentage rolls between min/max 2018-04-21 23:53:02 +02:00
willyb321
f3bc900f16 Merge branch 'release/2.9.6' 2018-04-22 07:00:47 +10:00
willyb321
38842417b0 bump 2018-04-22 06:59:08 +10:00
willyb321
82d485a98e fix preset rolls locked to low value 2018-04-22 06:49:43 +10:00
willyb321
fac71feea7 Lowercase armour check
Fixes #231
2018-04-21 12:56:51 +10:00
willyb321
ec148847a9 Merge branch 'develop' 2018-04-21 11:39:19 +10:00
willyb321
445c63878b add guard for tooltips crashing the import 2018-04-21 11:39:02 +10:00
willyb321
0474af912a Merge branch 'release/2.9.5' 2018-04-21 11:05:29 +10:00
willyb321
7df5953824 bump 2018-04-21 11:05:19 +10:00
willyb321
e391b563fb change preset rolls to 100%, 75%, 50% and random 2018-04-21 11:03:23 +10:00
willyb321
53f62f96d0 Merge branch 'master' into develop 2018-04-21 07:17:11 +10:00
willyb321
18745979a0 update about page 2018-04-20 19:20:47 +10:00
willyb321
c1966a38ff Merge branch 'master' into develop 2018-04-20 12:49:10 +10:00
willyb321
d86973f3b1 specify appVersion for bugsnag 2018-04-20 12:48:46 +10:00
willyb321
56b8d19649 Merge branch 'release/2.9.4' 2018-04-19 07:17:21 +10:00
willyb321
7cb037e0bc bump 2018-04-19 07:17:06 +10:00
willyb321
56e1b3f9e9 Revert "prep for guardian"
This reverts commit 7e67bd80dd.
2018-04-19 07:14:24 +10:00
willyb321
c792323a8a misc bits 2018-04-19 07:14:13 +10:00
willyb321
4d2865de13 fix resistance blueprint values 2018-04-19 07:14:02 +10:00
willyb321
0d360bc367 bugsnag update 2018-04-19 07:13:20 +10:00
willyb321
91c9b46b91 clean up webpack configs 2018-04-19 06:44:02 +10:00
willyb321
7e67bd80dd prep for guardian 2018-04-19 06:26:53 +10:00
willyb321
2f775ea09b Merge branch 'release/2.9.3' 2018-04-18 18:04:42 +10:00
willyb321
0603c55089 bump 2018-04-18 18:04:28 +10:00
willyb321
41f25a44e9 Merge branch 'master' into develop 2018-04-18 10:33:17 +10:00
willyb321
35440b7273 allow specials in internal and standard slots 2018-04-18 10:33:11 +10:00
willyb321
7d68b91018 Merge branch 'release/2.9.2' 2018-04-17 17:33:45 +10:00
willyb321
deaa61b848 bump version 2018-04-17 17:32:56 +10:00
willyb321
2d00cbc41b fix more values (hopefully done)
also, Fixes #247 by changing passengers to pax
2018-04-17 17:31:11 +10:00
willyb321
57c1e83c67 apply new experimentals structure 2018-04-17 15:01:09 +10:00
William
b7079dbd4e Merge pull request #246 from pnellesen/feature/bugfix_245
Fix for issue 245
2018-04-17 12:38:21 +10:00
willyb321
be642a5373 Merge branch 'master' into develop 2018-04-17 07:59:42 +10:00
willyb321
ffc691c1a2 Merge branch 'release/2.9.1' 2018-04-17 07:57:44 +10:00
willyb321
6122d99369 version bump 2018-04-17 07:57:13 +10:00
willyb321
b73a8bcdab hopefully fix some values and crashes
resistances are buggered on shields and armour by the looks of things
2018-04-17 07:55:17 +10:00
willyb321
dd624537b7 fix hardpoints / internals having the wrong mod applied 2018-04-17 07:50:57 +10:00
willyb321
50a67f73fd get rid of some redundant code 2018-04-17 07:50:13 +10:00
willyb321
2262a980d4 Merge remote-tracking branch 'origin/develop' into develop 2018-04-17 07:46:35 +10:00
willyb321
84964ceb5f add source maps to assist with debugging.
also upload them to bugsnag
2018-04-17 07:46:26 +10:00
William Blythe
c4bdb7a66e Merge remote-tracking branch 'origin/master' into develop 2018-04-16 13:11:44 +10:00
Pat
21309e129f Fix for issue 245 - check that the passenger property is defined for a slot before calculating total passenger capacity 2018-04-14 08:50:48 -05:00
willyb321
928e02c718 Merge branch 'release/2.9.0' 2018-04-14 16:53:08 +10:00
willyb321
7f494dd200 bump 2018-04-14 16:52:05 +10:00
William
f38e743e59 Merge pull request #244 from EDCD/feature/beyond-coriolis
Engineer mods.
2018-04-14 15:14:13 +10:00
Willyb321
58b55eb3da Engineers. Are. Back. Probably. 2018-04-14 14:50:37 +10:00
willyb321
8375ad95b3 Merge branch 'master' into feature/beyond-coriolis 2018-04-14 10:16:20 +10:00
willyb321
506d027a2d Merge branch 'develop' 2018-04-14 09:52:03 +10:00
willyb321
fbeb6237cf fix webpack 2018-04-14 09:51:46 +10:00
willyb321
bd95e2c5a5 Merge branch 'master' into develop 2018-04-14 09:48:03 +10:00
willyb321
f3276e557a Merge branch 'release/2.5.3' 2018-04-14 09:47:08 +10:00
willyb321
8b813e0e7f bump 2018-04-14 09:46:41 +10:00
willyb321
d660d2959f more roles fixing
use a smaller shield, add boosters, d rate some internals
2018-04-14 09:44:29 +10:00
willyb321
014ebda7d2 start fixing suggested roles. Thanks to rinzler + others 2018-04-14 08:06:44 +10:00
William
607398d364 Merge pull request #241 from Blackth0rn/fix/#240_incorrect_passenger_counts
Fix/#240 incorrect passenger counts
2018-04-13 16:20:52 +10:00
willyb321
97d141ce2b update lockfile version 2018-04-13 16:17:59 +10:00
willyb321
9f492db9c6 source map 2018-04-13 16:16:42 +10:00
willyb321
6ed82b366c update bugsnag 2018-04-13 16:16:01 +10:00
willyb321
a7ca037f48 loadout event prep 2018-04-13 14:38:22 +10:00
willyb321
6e21d0e74a Work on engineering import 2018-04-13 13:39:33 +10:00
willyb321
e6c75bf2af Merge branch 'develop' into feature/beyond-coriolis 2018-04-13 09:16:23 +10:00
willyb321
270c2f386e Merge branch 'release/2.5.2' 2018-04-13 07:56:38 +10:00
willyb321
83f7880c58 bump version 2018-04-13 07:51:07 +10:00
William
51d29aee38 Merge pull request #235 from pnellesen/feature/fueltank_column
Added fuel tank column to Core Module Classes on Shipyard
2018-04-13 07:38:07 +10:00
William
c7754c0365 Merge pull request #234 from joelgarboden/feature/remote_testing
Disable 'localhost' host header requirement
2018-04-13 07:34:59 +10:00
Greg Matthews
94037cea38 Fix #240: Added passenger count to individual ship page 2018-04-11 21:09:57 -07:00
Greg Matthews
5c1a9d9eea Fix #240: There is no size 7 passenger cabin so use the new findMaxInternal function to get the biggest one that fits in the slot 2018-04-11 21:07:00 -07:00
Greg Matthews
3835c73ffd Add findMaxInternal function to find the biggest module of a given group, rating, slotsize 2018-04-11 21:06:07 -07:00
Pat
b8c1effecb Update for issue #183 - use first letter of bulkhead instead of "Max Rating" value 2018-04-09 23:10:28 -05:00
Pat
dd07241dd9 Added fuel tank column to Core Module Classes on Shipyard 2018-04-07 18:57:54 -05:00
Joel Garboden
5ad828a613 Disable 'localhost' limit
Disale host header so webpack allows more than localhost access
2018-04-07 22:34:50 +00:00
Unknown
9856df5527 Portiguese 2018-03-22 22:00:19 +00:00
Unknown
7b249900ec Portuguese 2018-03-22 21:59:50 +00:00
Unknown
e3b9267c3f Merge remote-tracking branch 'origin/feature/beyond-coriolis' into develop 2018-03-22 21:59:37 +00:00
willyb321
a996b8135a inital engineerless import working 2018-03-23 08:51:48 +11:00
willyb321
5e1237390b Initial work on 3.0 imports.
Decided to split journal imports to a separate file.
2018-03-22 16:22:59 +11:00
Willyb321
83571b4bef add .editorconfig
Makes contributing and not messing up the codestyle easier
2018-03-22 12:07:19 +11:00
Willyb321
c2a0dad9a8 Export textarea select
Fixes #146
2018-03-22 12:06:36 +11:00
Unknown
f6f057689c Merge branch 'feature/chieftain' into develop 2018-01-27 11:47:17 +00:00
willyb321
4378f0020e add chieftain to map
also remove console logs that i forgot to remove
2018-01-27 10:55:37 +11:00
Unknown
2e9e7c4fc6 Merge branch 'release/2.5.1' 2018-01-24 09:18:58 +00:00
Unknown
b7b8ee5580 Merge branch 'release/2.5.1' into develop 2018-01-24 09:18:36 +00:00
Unknown
c630dbbed0 Version Bump 2018-01-24 09:18:21 +00:00
Unknown
fa6703a3b8 Merge remote-tracking branch 'Blackth0rn/feature/#186-passenger-totals' into develop 2018-01-24 08:45:54 +00:00
Unknown
bdcb64c9d1 Merge remote-tracking branch 'willyb321/feature/fix-engineer-values' into develop 2018-01-24 08:45:48 +00:00
Unknown
a1afc869a7 Merge remote-tracking branch 'Ghnuberath/feature/ax-modules' into develop 2018-01-24 08:45:44 +00:00
Unknown
d8ce26c7cf Merge remote-tracking branch 'origin/master' into develop 2018-01-24 08:44:45 +00:00
Sean McIntyre
95c474dc05 Recategorizing experimental modules into an "experimental" category 2018-01-22 16:48:10 -05:00
Sean McIntyre
56ca73b4ad Forgot Remote Release Flak Launcher 2018-01-22 16:43:29 -05:00
Sean McIntyre
d82cd6a89e Removing unnecessary change 2018-01-22 15:21:59 -05:00
Sean McIntyre
bf20d32364 Adding AX modules 2018-01-22 15:20:41 -05:00
willyb321
e968e62fca Fix clip size (hopefully) 2018-01-16 08:14:21 +11:00
Unknown
b9c9ca9fa1 Merge branch 'release/2.5.0' 2018-01-11 21:59:31 +00:00
Unknown
6e965e2e98 Version Bump 2018-01-11 21:59:20 +00:00
Unknown
7d569f9036 Merge branch 'feature/fix-engineers' into develop 2018-01-11 21:51:35 +00:00
Unknown
df5a77199d Merge branch 'develop' 2018-01-11 21:50:28 +00:00
Greg Matthews
3c8dfebfdc Issue #186. Added passenger totals to shipyard screen 2018-01-10 21:20:27 -08:00
Unknown
3cc422596f Something something consistant naming 2017-12-24 09:47:49 +00:00
Unknown
4ed167de22 Add T10 2017-12-24 09:47:33 +00:00
Unknown
b420647501 Something something consistant naming 2017-12-24 09:46:49 +00:00
Unknown
0c318b5e68 Add T10 2017-12-23 12:03:21 +00:00
willyb321
1c627297b8 shorten the terrible _addModifications call 2017-12-19 16:39:42 +11:00
willyb321
f41e2d0552 fix indent and hopefully fix ammo clip
dont know if it was broken, if someone has it DM it to me thanks
2017-12-19 16:28:05 +11:00
willyb321
1a1d539c60 fix engagement range slider and armour integrity
hopefully
2017-12-19 16:18:20 +11:00
willyb321
ba2e46f88f fix rate of fire
probably
2017-12-19 14:49:04 +11:00
willyb321
c2f1fa81af remove console logs 2017-12-17 18:48:24 +11:00
willyb321
752e03fa0f possible armour fix
need more data to confirm however
2017-12-17 18:24:34 +11:00
willyb321
1da69664d7 shields should be accurate now
dont now what the issue with hull is
if you see it let me know
2017-12-17 14:49:51 +11:00
willyb321
9aa986a133 thanks webstorm 2017-12-17 12:48:13 +11:00
willyb321
bdbfb28c4a missed a spot 2017-12-17 12:47:02 +11:00
willyb321
162156bb2b fix random weird indents 2017-12-17 12:46:18 +11:00
willyb321
64c5d542e9 more work on mods 2017-12-17 12:27:04 +11:00
willyb321
31dc789f6e initial (re)implementation of engineer mods
still some kinds to work out.
2017-12-17 11:16:50 +11:00
Unknown
b0e2cfd7db Merge branch 'feature/#112_ship_table_highlights' into develop 2017-12-04 09:41:54 +00:00
Greg Matthews
54ddb0d014 Rename sortValue and tmpSortValue to be more meaningful 2017-11-11 13:29:59 -08:00
Greg Matthews
e19688e96f Add alternate row highlighting for sorted value 2017-11-11 13:25:17 -08:00
Greg Matthews
4f53d75999 Fix whitespace in html 2017-11-11 13:24:57 -08:00
Unknown
7277460060 Merge branch 'release/2.4.2' 2017-11-09 17:23:46 +00:00
Unknown
93c4f6f3c0 Merge branch 'release/2.4.2' into develop 2017-11-09 17:23:32 +00:00
Unknown
c6919a7518 Bumps and changelog 2017-11-09 17:23:15 +00:00
Unknown
ca428e67dc Package Lock and D3 2017-11-09 17:18:55 +00:00
Unknown
c6726cf020 Merge remote-tracking branch 'BenJuan26/feature/fix-tests' into develop 2017-11-09 17:15:06 +00:00
Unknown
56ae1378da Merge branch 'feature/slot-commas' into develop 2017-11-09 17:14:49 +00:00
Unknown
e982ab1a3b Merge branch 'feature/ship-purchase-requirements' into develop 2017-11-09 17:11:32 +00:00
Unknown
0d6aa87e89 Update coriolis to use directory basis 2017-11-09 17:04:23 +00:00
Joshua Campbell
94d06e4025 Show ship purchase requirements on outfitting page 2017-11-07 01:46:58 -05:00
Benjamin Schubert
cee5b297ac Add create-react-class 2017-11-02 12:02:15 -04:00
Benjamin Schubert
c549213ce0 Switch to babel-preset-env 2017-11-02 11:44:53 -04:00
Benjamin Schubert
f3f9112767 Change jest version 2017-11-02 11:25:32 -04:00
Benjamin Schubert
fb325ea3e2 Add commas to slot cost 2017-11-01 15:56:51 -04:00
Unknown
3773f6f7ec Merge branch 'release/2.4.1' into develop 2017-10-31 16:52:51 +00:00
Unknown
b90ab6fe48 Merge branch 'release/2.4.1' 2017-10-31 16:52:34 +00:00
Unknown
cc2e56dc8a bump 2017-10-31 16:52:13 +00:00
William
ae65af7bbf prevent crashing when no modifications are in the data (#160) 2017-10-12 08:33:36 +01:00
William
c8fb513cd1 Fix repair limpet grouping (#161)
* Group repair limpets with the rest

* editor decided to add a random whitespace

* also it was set to tabs
2017-10-12 08:33:22 +01:00
Unknown
7fc3855af4 Merge branch 'release/2.4.0' into develop 2017-10-10 16:47:21 +01:00
Unknown
bcebb26d4a Merge branch 'release/2.4.0' 2017-10-10 16:46:57 +01:00
Unknown
1f23e4cfcc Version Bump 2017-10-10 16:46:34 +01:00
William
65998778fe fix zbuf error (#154)
Add an optional extended description…
2017-10-10 11:59:44 +01:00
Unknown
e53c04a07f Repair Limpet 2017-10-03 20:25:01 +01:00
Unknown
e5a8e106c1 Merge branch 'feature/languages' into develop 2017-09-01 20:56:34 +01:00
Unknown
11d1d80a53 Other languages JSON import 2017-09-01 20:55:11 +01:00
Unknown
c8a3d86a45 JSON Import 2017-09-01 20:52:45 +01:00
Unknown
4484ca226a Polish , Spanish, Italian JSON 2017-09-01 19:50:52 +01:00
Unknown
496c9ba35c Full bugsnag 2017-08-30 20:29:46 +01:00
Unknown
dae2fc9192 Bugsnag 2017-08-30 20:29:29 +01:00
Unknown
07b00e6230 Full bugsnag 2017-08-30 20:28:59 +01:00
Unknown
36526f0824 Merge branch 'feature/bugsnag' into develop 2017-08-30 19:47:52 +01:00
Unknown
f4691939ba Bugsnag 2017-08-30 19:46:36 +01:00
Unknown
534f735b63 Translation Advice 2017-08-30 19:33:42 +01:00
Unknown
bc2c8406a2 Russian JSON Generated 2017-08-30 19:30:27 +01:00
Unknown
032c44f39a German JSON Generated 2017-08-30 19:15:17 +01:00
Unknown
2b43c8e91a French JSON Generated 2017-08-30 19:04:57 +01:00
Unknown
e91b3df31a English JSON Generated 2017-08-30 19:04:49 +01:00
Unknown
3e0597023a Merge remote-tracking branch 'origin/master' 2017-08-29 20:06:30 +01:00
Unknown
1dd2edf742 Merge branch 'master' into develop 2017-08-29 20:05:51 +01:00
Unknown
97f3bece33 Merge branch 'release/2.3.7' 2017-08-29 20:05:28 +01:00
Unknown
be02444487 Changelog
Bump Version
2017-08-29 20:04:52 +01:00
Kamen Litchev
fc012fe68a Fix stroke-width handling bug in Safari 11 (#138)
* Fix stroke-width handling bug in Safari 11

* Further fix Safari 11 SVG stroke-width issues
2017-08-29 13:29:47 +01:00
strideynet
7ac16d6d22 Update Tracking 2017-08-22 21:03:23 +01:00
strideynet
4ee92b1f3e Update Tracking 2017-08-22 20:59:39 +01:00
strideynet
d82128690b Outdated URLs in tests don't match actual URLs 2017-08-17 19:47:13 +01:00
strideynet
9e57eb4262 Remove unwanted console.log and spacing fix 2017-08-17 15:00:13 +01:00
strideynet
6e0d45f419 ESLint issue
ESLint is picking a fault with the JSDoc despite it being correct. Will investigate further but for now this resolves the issue.
2017-08-17 14:42:44 +01:00
strideynet
645e86714e attempted fix for jsdoc detection 2017-08-17 14:31:00 +01:00
strideynet
00afd1cd6a Attempt to fix JSDoc 2017-08-17 14:12:25 +01:00
strideynet
77ae126a51 Compliance
As per #132
2017-08-17 14:05:22 +01:00
strideynet
9c82c7caed remove redundant LoC 2017-08-17 14:01:12 +01:00
strideynet
d9a92e7a78 Remove redundant LoC 2017-08-17 13:59:23 +01:00
strideynet
d8a87029a6 Compliance
As per #132
2017-08-17 13:42:12 +01:00
strideynet
f407d0f92a Compliance
Issue #132
2017-08-17 13:41:41 +01:00
strideynet
4ac42e62e6 Bumping NodeJS Version
See commit to travis.yml
2017-08-17 13:26:09 +01:00
strideynet
748c63fa0b Update NodeJS Version
Over the new few days I will test the newer major versions, for now just bumping to the latest V4 LTS
2017-08-17 13:24:21 +01:00
strideynet
78ca756cef Update package.json 2017-08-17 13:13:24 +01:00
strideynet
a8554b51c2 Uglify JS
Required for the build process for D3
2017-08-17 13:11:37 +01:00
sascha-dev
e54e4da289 Updated german translation (#127)
PR #127 merged to EDCD:Develop
2017-08-16 19:29:22 +01:00
Cmdr McDonald
f97cb5f5a7 Merge branch 'release/2.3.6' into develop 2017-06-24 09:34:41 +01:00
Cmdr McDonald
318e8077d0 Merge branch 'release/2.3.6' 2017-06-24 09:34:37 +01:00
Cmdr McDonald
c8355a532d Bump version 2017-06-24 09:33:21 +01:00
Cmdr McDonald
3686ccd4ed Update Russian translation 2017-06-24 09:24:38 +01:00
Cmdr McDonald
f02db0120a Fix for Spanish translation of Chaff Launcher 2017-06-24 09:18:27 +01:00
Cmdr McDonald
d9ad93d3cd Fix typo causing long range blueprint to not modify shot speed in some circumstances 2017-06-06 11:45:59 +01:00
Cmdr McDonald
77018cc1ad Fix issue where torpedo special effects were not showing 2017-05-30 19:44:35 +01:00
Cmdr McDonald
30a8a29ce3 Update miner role with better defaults 2017-05-30 19:44:11 +01:00
Cmdr McDonald
7b1aa646ac Fix 2017-05-17 20:56:13 +01:00
Cmdr McDonald
49e4409862 Merge branch 'release/2.3.5' into develop 2017-05-17 07:49:06 +01:00
Cmdr McDonald
ab87ddc649 Merge branch 'release/2.3.5' 2017-05-17 07:49:00 +01:00
Cmdr McDonald
57784329ac Bump version 2017-05-17 07:31:46 +01:00
Cmdr McDonald
1db613c56d Grey out modules that are powered off to provide a clearer visual indication 2017-05-16 20:31:56 +01:00
Cmdr McDonald
464cd4165f Added translation 2017-05-16 16:39:15 +01:00
Cmdr McDonald
420ebe4cc7 Merge branch 'feature/fixes' into develop 2017-05-11 13:14:27 +01:00
Cmdr McDonald
15ead440d6 Updated German translation; fix for #100 2017-05-11 13:14:19 +01:00
Cmdr McDonald
ef0614a62f Handle display when summary values show thrusters disabled but current mass keeps them enabled 2017-05-11 13:09:18 +01:00
Cmdr McDonald
2fd2af3e31 Ensure that hidden blueprint effects are applied when a blueprint is selected 2017-05-11 12:45:57 +01:00
Cmdr McDonald
f30a904a02 Remove logging 2017-05-03 19:18:50 +01:00
Cmdr McDonald
e00680113f Do not add hidden features 2017-05-03 19:15:07 +01:00
Cmdr McDonald
93e6a29057 Merge branch 'release/2.3.4' into develop 2017-05-03 17:12:16 +01:00
Cmdr McDonald
1fbba6a5f3 Merge branch 'release/2.3.4' 2017-05-03 17:12:13 +01:00
Cmdr McDonald
d1c0b635b3 Package bump 2017-05-03 17:12:07 +01:00
Cmdr McDonald
060cf75ec4 Handle different specials for dumbfire and seeker missiles 2017-05-03 12:37:49 +01:00
Cmdr McDonald
62e3b70581 Merge branch 'feature/edge' into develop 2017-04-28 07:20:23 -04:00
Cmdr McDonald
4accb901af Bump zlib 2017-04-26 07:58:05 -04:00
Cmdr McDonald
82725e1c5a Fixes 2017-04-25 12:10:44 +01:00
Cmdr McDonald
4dc8ab928c Remove findDOMNode as per warning 2017-04-23 18:50:13 +01:00
Cmdr McDonald
7db76ecba0 Lint fixes 2017-04-23 18:36:20 +01:00
Cmdr McDonald
16a5b2a72a Version bumps 2017-04-23 16:26:16 +01:00
Cmdr McDonald
59a5ba0227 Updaes 2017-04-23 16:06:14 +01:00
Cmdr McDonald
c130b52b7c Fix incorrect height of shipyard ship header 2017-04-21 22:39:07 +01:00
Cmdr McDonald
03f32097e9 Merge branch 'feature/ordering' into develop 2017-04-21 21:38:48 +01:00
Cmdr McDonald
8660dcbd2b Tidy up ordering 2017-04-21 21:38:42 +01:00
Cmdr McDonald
95f358f9d8 Upgrade to React 15.5 2017-04-21 16:07:41 +01:00
Cmdr McDonald
8f944bc4c8 Add racer role 2017-04-21 10:43:39 +01:00
Cmdr McDonald
e9cbe0d952 Bump package version 2017-04-20 15:11:59 +01:00
Cmdr McDonald
684ecfcafd Ensure that stock builds are saved with a code; ignore builds without codes in comparisons 2017-04-20 15:10:03 +01:00
Cmdr McDonald
1516bd4fc0 Fix crash when clearing a speical 2017-04-20 13:17:30 +01:00
Cmdr McDonald
4ea0bbfb93 Merge branch 'release/2.3.3' into develop 2017-04-15 20:46:50 +01:00
Cmdr McDonald
9b6df8ea4f Merge branch 'release/2.3.3' 2017-04-15 20:46:45 +01:00
Cmdr McDonald
9c6f5fb44e Bump version 2017-04-15 20:46:40 +01:00
Cmdr McDonald
c0a732a76b Merge branch 'feature/refit' into develop 2017-04-15 18:11:56 +01:00
Cmdr McDonald
9551b8b5de Add shopping list for refit 2017-04-15 18:11:50 +01:00
Cmdr McDonald
22f83b79af Encode blueprint information event when no active modifications 2017-04-15 18:11:09 +01:00
Cmdr McDonald
0a26f76fb4 Merge branch 'release/2.3.2' into develop 2017-04-14 13:09:55 +01:00
Cmdr McDonald
077f432daf Merge branch 'release/2.3.2' 2017-04-14 13:09:52 +01:00
Cmdr McDonald
e846b4508b Bump version 2017-04-14 11:06:09 +01:00
Cmdr McDonald
3be78885b8 Bump 2017-04-14 10:58:15 +01:00
Cmdr McDonald
e29a4b263d Fix scan time/scan rate confusion 2017-04-13 23:19:27 +01:00
Cmdr McDonald
061ab77de1 Merge branch 'release/23' into develop 2017-04-13 22:26:33 +01:00
Cmdr McDonald
c6a8e48fda Merge branch 'release/23' 2017-04-13 22:26:29 +01:00
Cmdr McDonald
f48af272c3 Bump for 2.3 2017-04-09 09:23:31 +01:00
Cmdr McDonald
2b1402d099 Bump for 2.3 2017-04-09 09:23:20 +01:00
Cmdr McDonald
1ad0afa0d7 Merge branch 'feature/fixes' into develop 2017-04-09 09:07:50 +01:00
Cmdr McDonald
93d6c1b871 Update defence tooltips - #93 2017-04-08 00:00:08 +01:00
Cmdr McDonald
243f1123ba Fix typo 2017-04-06 19:54:45 +01:00
Cmdr McDonald
2fa2625a8f Fix display and selection of modules near weight limit in menu 2017-04-04 23:55:08 +01:00
Cmdr McDonald
42c43d3f2d Use single calculation for shield metrics 2017-04-04 08:51:02 +01:00
Cmdr McDonald
1237833c7a Ensure that opponent piips are updated immediately when a new opponent is selecteD 2017-04-03 13:46:14 +01:00
Cmdr McDonald
171a84dd33 Add reload to hardpoint info 2017-04-01 21:20:44 +01:00
Cmdr McDonald
79dac7b38f Merge branch 'feature/fixes' into develop 2017-03-31 16:49:10 +01:00
Cmdr McDonald
0e86ae79c1 Revert 2.3 diminishing returns on boosters 2017-03-31 16:34:50 +01:00
Cmdr McDonald
7d7ea18447 Tidy-ups for modification 2017-03-31 12:28:29 +01:00
Cmdr McDonald
2a6da36aeb Fix names of features in tooltips 2017-03-31 12:04:38 +01:00
Cmdr McDonald
db1a976e20 Restyle modifications menu 2017-03-31 09:21:13 +01:00
Cmdr McDonald
3b058bda7f Consistency 2017-03-31 07:40:12 +01:00
Cmdr McDonald
da274f1b75 Fix issue for additive specials not showing up 2017-03-30 08:17:13 +01:00
Cmdr McDonald
d624663278 Remove unused components 2017-03-28 14:06:25 +01:00
Cmdr McDonald
d8ac185b4d Add optimal multiplier to thrusters 2017-03-27 11:04:17 +01:00
Cmdr McDonald
b66baef998 Add engineer info to blueprint tooltip 2017-03-27 10:17:30 +01:00
Cmdr McDonald
8a115f8323 Add component list to blueprint display tooltip 2017-03-26 20:48:21 +01:00
Cmdr McDonald
442da6f05b Use cargo hatch info on import if available 2017-03-25 18:19:31 +00:00
Cmdr McDonald
f536c037b1 Updates 2017-03-25 16:24:13 +00:00
Cmdr McDonald
33a678a200 Add SDPS totals 2017-03-24 22:30:55 +00:00
Cmdr McDonald
b0dc4d7864 Shirnk large labels on vertical bar chart 2017-03-24 12:04:41 +00:00
Cmdr McDonald
8b0e822ea9 Fixes 2017-03-23 10:14:43 +00:00
Cmdr McDonald
006811f5a9 Merge branch 'feature/battlecentre' into develop 2017-03-23 08:20:14 +00:00
Cmdr McDonald
873935c1d4 Update to documentation 2017-03-23 08:17:07 +00:00
Cmdr McDonald
94d876e934 Per-cell colour coding for blueprint tooltips 2017-03-22 21:38:33 +00:00
Cmdr McDonald
b055963fe0 Fixes 2017-03-22 18:34:57 +00:00
Cmdr McDonald
8ad9472d56 Provide correct blueprints limits for HRPs 2017-03-22 18:24:22 +00:00
Cmdr McDonald
d15e49f315 Ignore rpshot for eps and hps 2017-03-22 17:53:26 +00:00
Cmdr McDonald
c7ea1eb95a Use opponent's pip info 2017-03-22 14:19:21 +00:00
Cmdr McDonald
82ce86a374 Updates 2017-03-22 13:55:45 +00:00
Cmdr McDonald
a1a17bc836 Add blueprint info to modification tooltip 2017-03-22 11:44:08 +00:00
Cmdr McDonald
75a4e54453 Add tooltips for blueprints 2017-03-22 10:51:23 +00:00
Cmdr McDonald
32fb66139a Take base hull reinforcement resistances in to account when calculating modifiers 2017-03-22 09:20:51 +00:00
Cmdr McDonald
3f18987007 Rewrite vertical bar chart to use recharts 2017-03-21 14:46:35 +00:00
Cmdr McDonald
eb042b2778 Tidy-ups for build changes 2017-03-21 12:19:25 +00:00
Cmdr McDonald
85f108556a Fix for ship summary 2017-03-21 00:41:33 +00:00
Cmdr McDonald
73a75c69a3 Make various components stateless 2017-03-20 13:52:24 +00:00
Cmdr McDonald
2f5d123f02 Change chart label colour to black; persist select outfitting subpage 2017-03-20 07:48:44 +00:00
Cmdr McDonald
e278babee8 Use correct values for boost/speed when working out boost factor 2017-03-19 22:34:19 +00:00
Cmdr McDonald
86df6f20f6 Ensure ship picker z index is correct 2017-03-19 20:18:31 +00:00
Cmdr McDonald
117028875f Name change 2017-03-19 18:54:38 +00:00
Cmdr McDonald
cadd699bdf Manual merge of #90 2017-03-19 18:53:22 +00:00
Cmdr McDonald
6afd80002c Tidy-ups 2017-03-19 17:40:41 +00:00
Cmdr McDonald
2736e1df79 More metrics 2017-03-19 16:16:00 +00:00
Cmdr McDonald
0ff95ed1f1 Shrink movement profile 2017-03-19 08:49:38 +00:00
Cmdr McDonald
fcb8980a38 Tidy-ups 2017-03-19 08:41:36 +00:00
Cmdr McDonald
49a076fd9e Updates 2017-03-18 23:46:46 +00:00
Cmdr McDonald
eb83969015 Break out metric calculations 2017-03-18 13:42:32 +00:00
Cmdr McDonald
369d882354 Tidy-ups 2017-03-17 18:10:07 +00:00
Cmdr McDonald
23c4da55de Make ship summary numbers react to pips/fuel/cargo/etc 2017-03-17 14:04:07 +00:00
Cmdr McDonald
7259a666eb Tidy-ups 2017-03-17 12:34:36 +00:00
Cmdr McDonald
91cab5a4f1 Embed battle centre in main pages 2017-03-17 12:07:18 +00:00
Cmdr McDonald
d60a8f2625 Fix shield recovery/recharge calculations 2017-03-16 12:05:15 +00:00
Cmdr McDonald
69489aa267 Update shortcut information 2017-03-16 10:13:36 +00:00
Cmdr McDonald
750d23b10a Provide live sustained DPS for opponent 2017-03-16 07:35:52 +00:00
Cmdr McDonald
cd753af48e Fix issue putting 4A refinery in class 5 slot 2017-03-15 11:45:52 +00:00
Cmdr McDonald
365810a610 Working on shield recovery calculation 2017-03-15 11:40:12 +00:00
Cmdr McDonald
73d609610a Fix axis for effectice shield 2017-03-14 08:29:09 +00:00
Cmdr McDonald
a3c03266bf On-going tidy-ups 2017-03-13 22:59:01 +00:00
Cmdr McDonald
7b3ad555a1 Tidy-ups 2017-03-13 22:13:41 +00:00
Cmdr McDonald
1605e80884 Fix to calculate boost when engcap changes 2017-03-13 19:55:45 +00:00
Cmdr McDonald
0729fc29fa Add vertical bar chart and use it in battle centre 2017-03-13 17:07:39 +00:00
Cmdr McDonald
964cdd2b9a Move boost in to pips component 2017-03-12 19:11:40 +00:00
Cmdr McDonald
49c3e395db Merge branch 'feature/battlecentre' of https://github.com/EDCD/coriolis into feature/battlecentre 2017-03-12 18:37:21 +00:00
Cmdr McDonald
bdd2299335 Ignore swap files 2017-03-12 18:37:07 +00:00
Cmdr McDonald
f782adb21d Remove swap files 2017-03-12 18:37:07 +00:00
Cmdr McDonald
950c0c61f9 Start of shields component 2017-03-12 18:37:07 +00:00
Cmdr McDonald
f6ebaf7445 Set initial chart width 2017-03-12 18:37:07 +00:00
Cmdr McDonald
3b35d5030e Add ship picker 2017-03-12 18:37:07 +00:00
Cmdr McDonald
067b69f449 Link in components 2017-03-12 18:37:07 +00:00
Cmdr McDonald
6f67267fec Building components for battle centre 2017-03-12 18:37:07 +00:00
Cmdr McDonald
ec0cd37896 Merge branch 'feature/23' into develop 2017-03-12 18:36:45 +00:00
Cmdr McDonald
84719c997f Ignore swap files 2017-03-12 17:00:33 +00:00
Cmdr McDonald
2731ec3b90 Remove swap files 2017-03-12 17:00:19 +00:00
Cmdr McDonald
340121c6bd Start of shields component 2017-03-12 17:00:04 +00:00
Cmdr McDonald
ec4e70326a Set initial chart width 2017-03-11 22:22:12 +00:00
Cmdr McDonald
3a271e4b7b Add ship picker 2017-03-11 17:57:03 +00:00
Cmdr McDonald
02db800c7b Link in components 2017-03-11 13:28:24 +00:00
Cmdr McDonald
3a55c8cc0a Building components for battle centre 2017-03-10 17:17:37 +00:00
Cmdr McDonald
e00b07c2f6 Reset old modification values when a new roll is applied 2017-03-08 07:23:07 +00:00
Cmdr McDonald
fbfd0deb6c Show integrity 2017-03-06 09:19:05 +00:00
Cmdr McDonald
dc11cc182f Fixes 2017-03-04 15:42:24 +00:00
Cmdr McDonald
597344353a Update test with new hardness 2017-03-03 22:40:50 +00:00
Cmdr McDonald
9aef1ff8a6 Revert fixed header due to issues with ios 2017-03-03 22:03:47 +00:00
Cmdr McDonald
70f218f833 Tidy-ups for diffDetails 2017-03-03 21:57:00 +00:00
Cmdr McDonald
5937843be8 Fix PD/boost calculation 2017-03-02 14:37:03 +00:00
Cmdr McDonald
d4b384eded Updates for 2.3 2017-03-01 22:10:47 +00:00
Cmdr McDonald
2b5a42d4e2 Merge branch 'feature/x' into develop 2017-02-27 09:26:22 +00:00
Cmdr McDonald
afd8ad7678 Lint 2017-02-27 09:26:17 +00:00
Cmdr McDonald
cdc5c29458 Alter way that X axis for profile charts is calculated 2017-02-27 09:24:32 +00:00
Cmdr McDonald
5a351d4c0d Merge branch 'release/2.2.19' into develop 2017-02-26 21:26:00 +00:00
Cmdr McDonald
fac279d9dd Merge branch 'release/2.2.19' 2017-02-26 21:25:57 +00:00
Cmdr McDonald
f4e5254832 Relocate slider values 2017-02-26 21:22:53 +00:00
Cmdr McDonald
2fb3ee8cd8 Bump version 2017-02-26 21:14:12 +00:00
Cmdr McDonald
cc2f3fd1fe Merge branch 'feature/wp2' into develop 2017-02-26 21:06:17 +00:00
Cmdr McDonald
87146b2cf3 Add engine and FSD profiles 2017-02-26 21:06:07 +00:00
Cmdr McDonald
bec3ae3f89 Tidy ups 2017-02-26 09:14:10 +00:00
Cmdr McDonald
af2e0cbed3 Fixes 2017-02-25 22:18:09 +00:00
Cmdr McDonald
4bf30c0cd5 Updates 2017-02-25 14:45:47 +00:00
Cmdr McDonald
a9fdf73d86 Update to webpack 2 2017-02-25 13:15:24 +00:00
Cmdr McDonald
fe691d12c7 Remove shot speed modification 2017-02-23 18:55:44 +00:00
Cmdr McDonald
a5df542aa2 Power management default sort order is power usage in descending order 2017-02-22 10:44:56 +00:00
Cmdr McDonald
069959dabb Merge branch 'release/2.2.18' into develop 2017-02-22 08:29:47 +00:00
Cmdr McDonald
a0e8f19683 Merge branch 'release/2.2.18' 2017-02-22 08:29:43 +00:00
Cmdr McDonald
342ca7af05 Tidy-ups for release 2017-02-22 08:28:55 +00:00
Cmdr McDonald
d00c0c3904 Merge branch 'feature/ddgraph' into develop 2017-02-22 08:01:09 +00:00
Cmdr McDonald
8e0f1ca977 Tweaks 2017-02-22 07:58:51 +00:00
Cmdr McDonald
33360fd6cf Doc updates 2017-02-22 00:19:41 +00:00
Cmdr McDonald
78ad34b082 Update docs 2017-02-22 00:16:33 +00:00
Cmdr McDonald
ba98fe49a9 Reorder panels 2017-02-22 00:14:21 +00:00
Cmdr McDonald
11f5c04efa Updates for production version of d3 2017-02-21 23:54:27 +00:00
Cmdr McDonald
ee9f65052a Tidy-ups 2017-02-21 23:21:33 +00:00
Cmdr McDonald
a25dde8d2d Tweaks 2017-02-21 21:50:28 +00:00
Cmdr McDonald
8d813688f7 Merge branch 'feature/ddgraph' of https://github.com/EDCD/coriolis into feature/ddgraph 2017-02-21 21:36:41 +00:00
Cmdr McDonald
99fc55ba6c Fix up formats for d3 v4 2017-02-21 21:36:24 +00:00
Cmdr McDonald
402a4c1939 Move to newer version of d3 2017-02-21 21:36:24 +00:00
Cmdr McDonald
d1e16470b8 Fixes 2017-02-21 21:36:24 +00:00
Cmdr McDonald
76027b8537 Playing with damage dealt graph 2017-02-21 21:36:24 +00:00
Cmdr McDonald
fd5ff3b6a8 Fix up formats for d3 v4 2017-02-21 21:35:54 +00:00
Cmdr McDonald
d2d8f084d2 Move to newer version of d3 2017-02-21 19:22:14 +00:00
Cmdr McDonald
ea3d57399c Tidy up available modules layout 2017-02-21 16:27:09 +00:00
Cmdr McDonald
77d3053ff8 Handle 'higher better' modifications when both min and max are negative - fixes #80 2017-02-20 14:05:19 +00:00
Cmdr McDonald
aea3e43e1c Fixes and tidy-ups 2017-02-19 20:49:31 +00:00
Cmdr McDonald
a949bd6738 Merge branch 'feature/explorer' into develop 2017-02-19 19:26:32 +00:00
Cmdr McDonald
4981ffb908 Make integrity for module reinforcement packages visible 2017-02-19 19:26:16 +00:00
Cmdr McDonald
4859138053 Tidy up layout for module selection 2017-02-19 19:25:50 +00:00
Cmdr McDonald
1f3c66d9ba Tidy up explorer role 2017-02-19 12:50:21 +00:00
Cmdr McDonald
f2af463d00 Remove cruft 2017-02-19 12:50:09 +00:00
Cmdr McDonald
3858712613 Hardcode shield translation for internal slot optmul 2017-02-18 21:29:21 +00:00
Cmdr McDonald
d7941e0a8a Merge branch 'release/2.2.17' into develop 2017-02-18 10:00:14 +00:00
Cmdr McDonald
178d38f28d Merge branch 'release/2.2.17' 2017-02-18 10:00:10 +00:00
Cmdr McDonald
e42a0d1210 Bump 2017-02-18 10:00:07 +00:00
Cmdr McDonald
ddb89c47e7 Merge branch 'feature/multicrew' into develop 2017-02-18 09:51:24 +00:00
Cmdr McDonald
8b9aae342b Fix incorrect terminology for shield metrics. Fixes #5 2017-02-18 09:07:18 +00:00
Cmdr McDonald
ab1d73a6ea Add crew 2017-02-16 22:43:06 +00:00
Cmdr McDonald
d73a3cc2b4 Merge branch 'release/2.2.16' into develop 2017-02-15 20:44:21 +00:00
Cmdr McDonald
230351b959 Merge branch 'release/2.2.16' 2017-02-15 20:44:17 +00:00
Cmdr McDonald
4ee5c03cd1 Bump version 2017-02-15 20:44:09 +00:00
Cmdr McDonald
d8e9733170 Fix issue where extreme values didn't know if a given value was higher == better or higher == worse 2017-02-15 19:10:58 +00:00
Cmdr McDonald
aba2abe507 Merge branch 'release/2.2.15' into develop 2017-02-13 19:50:55 +00:00
Cmdr McDonald
45e6b71ec9 Merge branch 'release/2.2.15' 2017-02-13 19:50:51 +00:00
Cmdr McDonald
15a14dc280 Package version 2017-02-13 19:50:47 +00:00
Cmdr McDonald
cbac650b9e Added miner and shielded miner roles 2017-02-13 08:07:35 +00:00
Cmdr McDonald
f011f1f4d5 Add ability to export module list to EDDB to find where they can be purchased 2017-02-13 07:59:29 +00:00
Cmdr McDonald
d467ad5f7c Merge branch 'feature/fixes' into develop 2017-02-12 11:11:10 +00:00
Cmdr McDonald
b56ac177d9 Merge branch 'willyb321-master' into feature/fixes 2017-02-12 11:08:05 +00:00
Cmdr McDonald
5b13d64a1d Merge branch 'master' of https://github.com/willyb321/coriolis into willyb321-master 2017-02-12 11:07:46 +00:00
Cmdr McDonald
2620935745 Add additional explanation for import failures 2017-02-12 08:31:09 +00:00
Cmdr McDonald
45852db507 Add 'Extreme' roll 2017-02-11 19:57:46 +00:00
Cmdr McDonald
7fbcbb75ed Do not rely on slot sizes being correct 2017-02-11 19:45:28 +00:00
Cmdr McDonald
abf65ee436 Reload page if Safari throws a security error 2017-02-10 18:22:39 +00:00
Cmdr McDonald
24849cee08 Ensure that standard slots are repainted when any component changes 2017-02-08 10:09:30 +00:00
Cmdr McDonald
9e5efe50dc Merge branch 'release/2.2.14' into develop 2017-02-08 09:29:08 +00:00
Cmdr McDonald
39e9a38068 Merge branch 'release/2.2.14' 2017-02-08 09:29:04 +00:00
Cmdr McDonald
73d5915b3a Package bump 2017-02-08 09:27:27 +00:00
Cmdr McDonald
b06d2d1f72 Merge branch 'feature/mods' into develop 2017-02-08 09:26:09 +00:00
Cmdr McDonald
6e71f5e6db Use ship name rather than model if possible 2017-02-08 09:24:13 +00:00
William
588cfc3990 fix wiki link 2017-02-08 16:14:34 +11:00
Cmdr McDonald
191e31ff18 Use new-style blueprint data; fix numeric special effect calculations 2017-02-05 15:38:04 +00:00
Cmdr McDonald
c655b65779 Ensure TTD is recalculated when EPS is recalculated 2017-02-02 23:40:24 +00:00
Cmdr McDonald
3e168a3e5f Merge branch 'release/2.2.13' into develop 2017-02-02 23:05:50 +00:00
Cmdr McDonald
ee52a4b716 Merge branch 'release/2.2.13' 2017-02-02 23:05:46 +00:00
Cmdr McDonald
a14974fef4 Bump package 2017-02-02 23:03:35 +00:00
Cmdr McDonald
2c0959d68b Merge branch 'feature/fixed' into develop 2017-02-02 23:01:08 +00:00
Cmdr McDonald
0221d05b04 Tweak recalculation of mass 2017-02-01 08:57:56 +00:00
Cmdr McDonald
975432b45a Lint 2017-02-01 08:33:16 +00:00
Cmdr McDonald
e78551f5d9 Ensure that ship mass is recalculated when appropriate - #67 2017-02-01 08:24:50 +00:00
Cmdr McDonald
b1daf6f799 Fix issue with auto loader not showing - #66 2017-02-01 07:45:44 +00:00
Cmdr McDonald
bc31be5884 Fixes 2017-02-01 07:32:33 +00:00
Cmdr McDonald
df03102c35 Added time to drain calculation and display 2017-01-30 21:31:55 +00:00
Cmdr McDonald
55e4c51d77 Playing with damage dealt graph 2017-01-30 13:34:41 +00:00
Cmdr McDonald
e7d1a0df63 Merge branch 'release/2.2.12' into develop 2017-01-29 08:27:28 +00:00
Cmdr McDonald
ca0c9675b3 Merge branch 'release/2.2.12' 2017-01-29 08:27:24 +00:00
Cmdr McDonald
491899fe49 Package bump 2017-01-29 08:27:17 +00:00
Cmdr McDonald
cc22a89ad6 Merge branch 'feature/specials' into develop 2017-01-29 08:26:34 +00:00
Cmdr McDonald
d389114189 Remove extraneous functions 2017-01-28 20:40:58 +00:00
Cmdr McDonald
9d0f7a166b Don't show both power generation and power draw in diffDetails 2017-01-27 22:23:36 +00:00
Cmdr McDonald
c344d56d48 Merge branch 'feature/specials' of https://github.com/EDCD/coriolis into feature/specials 2017-01-27 21:34:33 +00:00
Cmdr McDonald
eb5d0e868c Remove logging 2017-01-27 21:34:27 +00:00
Cmdr McDonald
c28f22a78b Add units for time 2017-01-27 21:34:27 +00:00
Cmdr McDonald
e2b22dd4ca Alter shortlink hotkey - for #58 2017-01-27 21:32:07 +00:00
Cmdr McDonald
f5ecccec48 Add weapon mods to damage dealt 2017-01-27 21:32:07 +00:00
Cmdr McDonald
7c8eee4707 Add package info 2017-01-27 21:32:07 +00:00
Cmdr McDonald
99e251fd4b Fix missing break in case 2017-01-27 21:32:07 +00:00
Cmdr McDonald
c1ddecfd3e Add specials 2017-01-27 21:32:07 +00:00
Cmdr McDonald
0ae59c5e48 Remove old reference to coriolis.io 2017-01-27 21:32:07 +00:00
Cmdr McDonald
eb76040b08 Remove logging 2017-01-27 21:31:05 +00:00
Cmdr McDonald
7698cb75e9 Add units for time 2017-01-27 21:30:15 +00:00
Cmdr McDonald
bd7139c703 Merge branch 'feature/specials' of https://github.com/EDCD/coriolis into feature/specials 2017-01-27 09:54:26 +00:00
Cmdr McDonald
c9b9404ba1 Alter shortlink hotkey - for #58 2017-01-27 09:54:17 +00:00
Cmdr McDonald
84ff013a9e Add weapon mods to damage dealt 2017-01-27 09:54:17 +00:00
Cmdr McDonald
d2bcf6cd71 Add package info 2017-01-27 09:54:17 +00:00
Cmdr McDonald
e52c6730f4 Fix missing break in case 2017-01-27 09:54:17 +00:00
Cmdr McDonald
e448b3cb0a Add specials 2017-01-27 09:54:17 +00:00
Cmdr McDonald
705f3158aa Remove old reference to coriolis.io 2017-01-27 09:54:17 +00:00
Cmdr McDonald
f4789717fe Alter shortlink hotkey - for #58 2017-01-27 09:54:08 +00:00
Cmdr McDonald
93e69f215b Add weapon mods to damage dealt 2017-01-27 09:46:32 +00:00
Cmdr McDonald
d04e662de4 Merge branch 'hotfix/2.2.11b' into develop 2017-01-27 08:14:37 +00:00
Cmdr McDonald
a9f9285278 Merge branch 'hotfix/2.2.11b' 2017-01-27 08:14:26 +00:00
Cmdr McDonald
c5e27d43aa Do not apply dropoff twice when calculating weapon effectiveness 2017-01-27 08:14:22 +00:00
Cmdr McDonald
db376829f5 Add package info 2017-01-27 07:56:50 +00:00
Cmdr McDonald
79a31fd992 Fix missing break in case 2017-01-26 18:01:22 +00:00
Cmdr McDonald
094f3aa3e2 Add specials 2017-01-26 16:06:29 +00:00
Cmdr McDonald
41d51743d3 Remove old reference to coriolis.io 2017-01-26 14:38:23 +00:00
Cmdr McDonald
cf741d18ed Merge branch 'release/2.2.11' into develop 2017-01-25 19:46:52 +00:00
Cmdr McDonald
c02f674530 Merge branch 'release/2.2.11' 2017-01-25 19:46:47 +00:00
Cmdr McDonald
89f2273b5d Tidy-ups 2017-01-25 19:44:36 +00:00
Cmdr McDonald
351b084ce9 Merge branch 'feature/help' into develop 2017-01-25 19:34:06 +00:00
Cmdr McDonald
dd48d2d007 Increase falloff with range for focused mods 2017-01-25 19:08:55 +00:00
Cmdr McDonald
63e850b5aa Fixes for fragment cannons 2017-01-25 14:23:57 +00:00
Cmdr McDonald
46775879f7 Add EDMC import help 2017-01-25 11:07:17 +00:00
Cmdr McDonald
c9ba4e3fcb Remove editor file 2017-01-25 11:00:10 +00:00
Cmdr McDonald
8b855b62a1 Fix tests with new values 2017-01-25 10:59:28 +00:00
Cmdr McDonald
d6a687300c Add engagement range to damage received panel 2017-01-25 10:52:09 +00:00
Cmdr McDonald
ddc968129d Damage dealt only shows enabled weapons 2017-01-25 10:20:28 +00:00
Cmdr McDonald
e364560ca7 Provide damage dealt statistics for both shields and hull 2017-01-25 10:11:24 +00:00
Cmdr McDonald
baf6f8130a Tidy up spacing for movement summary 2017-01-25 09:25:01 +00:00
Cmdr McDonald
f8f5cd2581 Add 'average' roll for blueprints 2017-01-25 09:20:01 +00:00
Cmdr McDonald
8019453d0d Make absolute damage visible 2017-01-25 08:01:20 +00:00
Cmdr McDonald
6ad17595dc Merge branch 'hotfix/2.2.10c' into develop 2017-01-24 11:49:24 +00:00
Cmdr McDonald
50e1f7d4df Lint fixes 2017-01-24 11:41:50 +00:00
Cmdr McDonald
fdb931fe00 Merge branch 'feature/help' of https://github.com/EDCD/coriolis into feature/help 2017-01-23 14:21:17 +00:00
Cmdr McDonald
86efb59237 Add beta flag 2017-01-23 14:21:05 +00:00
Cmdr McDonald
f05aa66e53 Add help system and initial help file 2017-01-23 14:21:05 +00:00
Cmdr McDonald
f4e95cd150 Add framework for help 2017-01-23 14:21:05 +00:00
Cmdr McDonald
5798c0e8a6 Add help system and initial help file 2017-01-23 14:20:06 +00:00
Cmdr McDonald
22f98fa8a7 Add framework for help 2017-01-23 14:19:43 +00:00
Cmdr McDonald
897668c0d4 Merge branch 'hotfix/2.2.10b' into develop 2017-01-23 14:18:50 +00:00
Cmdr McDonald
99c0bfcee1 Merge branch 'hotfix/2.2.10b' 2017-01-23 14:17:50 +00:00
Cmdr McDonald
37bfc700e9 Fix for direct entering of modifications 2017-01-23 14:17:42 +00:00
Cmdr McDonald
8fcebf59f6 Add beta flag 2017-01-23 13:17:53 +00:00
Cmdr McDonald
486c055631 Merge branch 'feature/help' of https://github.com/EDCD/coriolis into feature/help 2017-01-23 13:16:22 +00:00
Cmdr McDonald
92246302b9 Add help system and initial help file 2017-01-23 13:15:45 +00:00
Cmdr McDonald
0d646c6193 Add framework for help 2017-01-23 13:14:59 +00:00
Cmdr McDonald
217ea3c5ac Merge branch 'release/2.10' into develop 2017-01-23 13:05:32 +00:00
Cmdr McDonald
fcf0494df6 Merge branch 'release/2.10' 2017-01-23 13:05:21 +00:00
Cmdr McDonald
20ba6eb822 Tidy-ups prior to release 2017-01-23 13:05:14 +00:00
Cmdr McDonald
0e2c0349e0 Merge branch 'feature/blueprints' into develop 2017-01-22 17:16:40 +00:00
Cmdr McDonald
c49e2cff03 Lint and test fixes 2017-01-22 17:12:48 +00:00
Cmdr McDonald
d79313bfbe Fix shield boost values; add tooltips 2017-01-22 14:15:33 +00:00
Cmdr McDonald
fd404b5155 Handle heavy duty shield booster special modification value 2017-01-22 10:59:36 +00:00
Cmdr McDonald
49e72146b4 Only update relevant modification parameters 2017-01-22 09:02:32 +00:00
Cmdr McDonald
9b534b62c8 Add modification functionality 2017-01-21 10:34:08 +00:00
Cmdr McDonald
0be59af9b0 Allow selection of blueprints type and grade 2017-01-20 17:53:49 +00:00
Cmdr McDonald
5d87a6cd56 Add help system and initial help file 2017-01-20 11:56:32 +00:00
Cmdr McDonald
c61c17d465 Add framework for help 2017-01-19 22:25:51 +00:00
Cmdr McDonald
029ba63aa5 Use new-style modification information 2017-01-19 22:25:13 +00:00
Cmdr McDonald
99e9e0c76f Merge branch 'feature/fixes' into develop 2017-01-15 23:06:13 +00:00
Cmdr McDonald
5bbc6e1cbe Use damage distribution numbers 2017-01-15 17:10:46 +00:00
Cmdr McDonald
1e5f66e528 Fix detailed export of module reinforcement packages 2017-01-15 17:07:12 +00:00
Cmdr McDonald
cdb837a25a Merge branch 'release/2.2.9' into develop 2017-01-14 16:24:18 +00:00
Cmdr McDonald
dd1175abf4 Merge branch 'release/2.2.9' 2017-01-14 16:24:09 +00:00
Cmdr McDonald
619976230d Bump version 2017-01-14 16:24:04 +00:00
Cmdr McDonald
2cb0d5209b Merge branch 'feature/falloff' into develop 2017-01-14 16:23:12 +00:00
Cmdr McDonald
ad06e23afa Add total DPS and effectiveness information to 'Damage Dealt' section 2017-01-14 16:20:48 +00:00
Cmdr McDonald
3def84e435 Use better DPE calculation methodology 2017-01-14 13:15:15 +00:00
Cmdr McDonald
7f377d6345 Add and use range when calculating weapon effectiveness for damage dealt 2017-01-14 13:10:09 +00:00
Cmdr McDonald
53137e0ae1 Add falloff for weapons 2017-01-14 09:52:31 +00:00
Cmdr McDonald
792eda2572 Use SSL-enabled server for shortlinks 2017-01-13 20:05:45 +00:00
Cmdr McDonald
550c94fa94 Merge branch 'release/2.2.8' into develop 2017-01-13 11:42:36 +00:00
Cmdr McDonald
2a841281d4 Merge branch 'release/2.2.8' 2017-01-13 11:42:31 +00:00
Cmdr McDonald
260f29834a Update release notes 2017-01-13 11:35:01 +00:00
Cmdr McDonald
ddb35d321c Fix issue where filling all internals with cargo racks would include restricted slots 2017-01-13 11:34:18 +00:00
Cmdr McDonald
6017c1ecff Merge branch 'release/2.2.7' into develop 2017-01-11 21:59:09 +00:00
Cmdr McDonald
05e06f30f5 Merge branch 'release/2.2.7' 2017-01-11 21:59:01 +00:00
Cmdr McDonald
fb5ba6a0b2 Update damage dealt to use actual resistances 2017-01-11 21:57:49 +00:00
Cmdr McDonald
80656a7a78 Fix resistance diminishing return calculations 2017-01-11 21:33:31 +00:00
Cmdr McDonald
ce980cf091 Merge branch 'release/2.2.6' into develop 2017-01-10 19:34:18 +00:00
Cmdr McDonald
a4656e223a Merge branch 'release/2.2.6' 2017-01-10 19:34:10 +00:00
Cmdr McDonald
66d4b5ac4c Fix tests due to new module values 2017-01-10 19:30:43 +00:00
Cmdr McDonald
6961469ae5 Merge branch 'feature/changes' into develop 2017-01-10 19:15:56 +00:00
Cmdr McDonald
06f4abdf8b Import builds with military slots 2017-01-10 19:12:19 +00:00
Cmdr McDonald
7855d0e171 Reinstate jump range graph 2017-01-02 12:06:44 +00:00
Cmdr McDonald
40f213c883 Do not lose ship selector selection on narrow screens 2017-01-02 12:05:55 +00:00
Cmdr McDonald
be1bfeb6f3 Ensure that information is not lost on narrow screens. Fix for #48 2017-01-02 12:05:22 +00:00
Cmdr McDonald
b40a2e96e0 Fix for change to military slots 2016-12-24 23:49:33 +00:00
Cmdr McDonald
5f036c586c Use own URL shortener 2016-12-24 21:57:11 +00:00
Cmdr McDonald
091789c819 Revert overloading of 'reload' translation key 2016-12-24 20:57:51 +00:00
Cmdr McDonald
0ce8bfac79 Fixes for #46 2016-12-24 20:56:37 +00:00
Cmdr McDonald
e53ffd0273 Update shield recovery/regeneration calculations 2016-12-20 13:58:00 +00:00
Cmdr McDonald
bb7db144d6 Allow collapse/expand of damage sections 2016-12-19 21:50:53 +00:00
Cmdr McDonald
2e42a328e0 Add base resistances to defence summary tooltip 2016-12-19 17:56:54 +00:00
Cmdr McDonald
f82122f29f Add module copy functionality - drag module whilst holding 'alt' to copy 2016-12-18 21:37:21 +00:00
Cmdr McDonald
5bf907809d Make weapons real modules to benefits from standard DPS etc. calculations 2016-12-18 21:26:30 +00:00
Cmdr McDonald
51d7b6c9aa Add 'Piercing' information to hardpoints; add 'Hardness' information to ship summary 2016-12-18 09:36:33 +00:00
Cmdr McDonald
b8cff0c2fc Add 'Damage received' section 2016-12-17 10:46:52 +00:00
Cmdr McDonald
6ac69a6388 Add translation 2016-12-16 21:16:17 +00:00
Cmdr McDonald
32282141cf Use ship rather than ship ID 2016-12-16 21:09:11 +00:00
Cmdr McDonald
059c2badf4 Lint 2016-12-16 20:38:58 +00:00
Cmdr McDonald
fb090618da Add 'Damage dealt' section 2016-12-16 20:37:06 +00:00
Cmdr McDonald
9ed0e30538 Add hardness to shipyard 2016-12-16 16:25:57 +00:00
Cmdr McDonald
af82b8ca1e Fix up tests 2016-12-15 16:46:38 +00:00
Cmdr McDonald
6e18793d82 Lint fix 2016-12-14 22:41:10 +00:00
Cmdr McDonald
22e74164c5 Alternate (embedded) code versioning scheme; start to fix up tests 2016-12-14 22:38:21 +00:00
Cmdr McDonald
93ba1bf67a Do not include disabled shield boosters in calculations 2016-12-14 17:39:01 +00:00
Cmdr McDonald
46ed9003dd Tidy-ups 2016-12-14 13:31:20 +00:00
Cmdr McDonald
5603315bf0 Version URLs to handle changes to ship specifications over time 2016-12-14 13:23:54 +00:00
Cmdr McDonald
5bbc6be3d8 Obey restricted slot rules when adding all for internal slots 2016-12-14 13:23:19 +00:00
Cmdr McDonald
203e9c7b46 Fix for importing definitions with missing slots 2016-12-14 08:49:23 +00:00
Cmdr McDonald
2a6850ded0 Set initial shield boost correctly 2016-12-13 22:08:49 +00:00
Cmdr McDonald
041f873f97 Updates 2016-12-13 18:56:59 +00:00
Cmdr McDonald
b944035541 Use separate speed/rotation/acceleration multipliers for thrusters if available 2016-12-12 13:37:36 +00:00
Cmdr McDonald
7c6a4fc5f8 Do not rely on coriolis-data' internal ordering of modules for display purposes 2016-12-12 10:38:50 +00:00
Cmdr McDonald
5426b55637 Ensure module ordering is consistent 2016-12-12 10:28:54 +00:00
Cmdr McDonald
a6a10df39c Add movement summary; add standard internal class sizes to shipyard page; fix issue when importing Viper Mk IV 2016-12-11 21:52:49 +00:00
Cmdr McDonald
0dc58bad7e Beta version 2016-12-03 15:52:17 +00:00
Cmdr McDonald
794faacbd4 Merge branch 'release/2.2.5' into develop 2016-12-03 15:48:50 +00:00
Cmdr McDonald
0a37b36ec2 Merge branch 'release/2.2.5' 2016-12-03 15:48:45 +00:00
Cmdr McDonald
85e6796e88 Bump version 2016-12-03 15:48:39 +00:00
Cmdr McDonald
fa1ef47b71 Add note to disable ghostery in error situations 2016-12-01 13:26:33 +00:00
Cmdr McDonald
f31e3c09f4 Merge branch 'feature/rebalance' into develop 2016-12-01 10:28:21 +00:00
Cmdr McDonald
e6ab536601 Merge branch 'feature/rebalance' of https://github.com/EDCD/coriolis into feature/rebalance 2016-12-01 10:28:08 +00:00
Cmdr McDonald
5bced9fe56 Bump version number 2016-11-30 15:09:25 +00:00
Cmdr McDonald
67742060d3 Merge branch 'feature/burst' into develop 2016-11-30 15:09:04 +00:00
Cmdr McDonald
ca2136544c Handle unmodifiable values 2016-11-30 15:08:32 +00:00
Cmdr McDonald
ee19e9af50 Lint 2016-11-30 14:56:33 +00:00
Cmdr McDonald
f457fd0bff Fixes for burst calculations 2016-11-30 14:51:19 +00:00
Cmdr McDonald
c1ce07e039 Calculate rate of fire for multi-burst weapons 2016-11-30 12:19:40 +00:00
Cmdr McDonald
c8d1536f77 Bump version number 2016-11-29 13:53:23 +00:00
Cmdr McDonald
231ad4af59 Merge branch 'release/2.2.4' into develop 2016-11-28 16:54:35 +00:00
Cmdr McDonald
d5f61d7ae8 Merge branch 'release/2.2.4' 2016-11-28 16:54:32 +00:00
Cmdr McDonald
8a5d4a36bf Bumped revision 2016-11-28 16:51:24 +00:00
Cmdr McDonald
2c9237626d Show specials; handle import of specials 2016-11-27 19:43:43 +00:00
Cmdr McDonald
37f889e317 Merge branch 'feature/incendiary' into develop 2016-11-26 23:24:37 +00:00
Cmdr McDonald
f86ba48295 Fix tests 2016-11-26 18:58:29 +00:00
Cmdr McDonald
aac35633a3 Merge branch 'feature/incendiary' of https://github.com/EDCD/coriolis into feature/incendiary 2016-11-26 18:56:57 +00:00
Cmdr McDonald
e73e0a305d Use new-style modification data 2016-11-26 18:54:22 +00:00
Cmdr McDonald
4b2b0efe37 Allow non-numeric modifiers 2016-11-26 18:54:22 +00:00
Cmdr McDonald
8fe20f6f65 Checkpoint - handle non-numeric modifiers 2016-11-26 18:54:22 +00:00
Cmdr McDonald
11af7f567a Move to method for damage type to allow for modifications 2016-11-26 18:54:22 +00:00
Cmdr McDonald
3b8444482f Add URL shortlink for outfitting page 2016-11-26 18:53:47 +00:00
Cmdr McDonald
c09e1b1b3e Merge branch 'feature/incendiary' of https://github.com/EDCD/coriolis into feature/incendiary 2016-11-26 13:16:38 +00:00
Cmdr McDonald
5770cf8d39 Use new-style modification data 2016-11-26 13:15:43 +00:00
Cmdr McDonald
6da09f2e5d Allow non-numeric modifiers 2016-11-26 13:15:43 +00:00
Cmdr McDonald
294fadf7cd Checkpoint - handle non-numeric modifiers 2016-11-26 13:15:43 +00:00
Cmdr McDonald
2a97678574 Move to method for damage type to allow for modifications 2016-11-26 13:15:43 +00:00
Cmdr McDonald
76b3bd34f5 Use new-style modification data 2016-11-26 12:54:22 +00:00
Cmdr McDonald
02bfecb92d Allow non-numeric modifiers 2016-11-24 22:23:14 +00:00
Cmdr McDonald
719759ad56 Merge branch 'release/2.2.3' into develop 2016-11-24 14:37:43 +00:00
Cmdr McDonald
fd446b29ba Merge branch 'release/2.2.3' 2016-11-24 14:37:39 +00:00
Cmdr McDonald
e5552d3e10 Updates ready for release 2016-11-24 13:49:37 +00:00
Cmdr McDonald
50946eeeb8 Fix misnamed diamondbacks - issue #36 2016-11-24 13:19:14 +00:00
Cmdr McDonald
faab41117c Checkpoint - handle non-numeric modifiers 2016-11-24 12:50:33 +00:00
Cmdr McDonald
0ab59c1f9a Handle import of restricted slots - fix for #35 2016-11-24 11:10:41 +00:00
Cmdr McDonald
1067dceaa3 Move to method for damage type to allow for modifications 2016-11-23 13:02:23 +00:00
Cmdr McDonald
9042de422a patch 2016-11-23 01:05:13 +00:00
Cmdr McDonald
f0547feb93 Merge branch 'feature/blueprints' into develop 2016-11-23 00:56:19 +00:00
Cmdr McDonald
f863daa347 Fix hull boost calculation. Partial fix for #29 2016-11-23 00:54:42 +00:00
Cmdr McDonald
fdb202e7d6 Add blueprints 2016-11-22 15:52:31 +00:00
Cmdr McDonald
c6bde19052 Merge branch 'release/2.2.2' into develop 2016-11-21 16:44:19 +00:00
Cmdr McDonald
f6aff3d3bb Merge branch 'release/2.2.2' 2016-11-21 16:44:15 +00:00
Cmdr McDonald
2f4a2ebe03 Bumped release 2016-11-21 16:42:17 +00:00
Cmdr McDonald
ca20e94b93 Merge branch 'feature/res' into develop 2016-11-21 16:39:25 +00:00
Cmdr McDonald
40a87dceeb Update tooltip to match reality 2016-11-21 11:34:45 +00:00
Cmdr McDonald
95b7d60be4 Fix URL strings for query parameter method 2016-11-21 11:33:34 +00:00
Cmdr McDonald
24abd6583f Remove requirement for double encoding 2016-11-21 10:14:16 +00:00
Cmdr McDonald
8857aba53f Use query parameters rather than long path 2016-11-21 10:06:14 +00:00
Cmdr McDonald
e4830811b0 Fix up jitter 2016-11-17 14:23:40 +00:00
Cmdr McDonald
143380ac58 Tidy-ups 2016-11-16 20:58:35 +00:00
Cmdr McDonald
a2f6fb6ac0 Added help tooltip for modifications 2016-11-16 20:49:22 +00:00
Cmdr McDonald
0d3c128059 Lints and tests 2016-11-15 13:38:02 +00:00
Cmdr McDonald
930a555425 Update costs for reload 2016-11-15 13:33:59 +00:00
Cmdr McDonald
d6f213fbe7 Remove logging 2016-11-14 23:02:56 +00:00
Cmdr McDonald
0571e8e099 Added jitter for hardpoints 2016-11-14 22:58:09 +00:00
Cmdr McDonald
54c61ecb7d Fix base armour 2016-11-14 22:18:16 +00:00
Cmdr McDonald
030867c4f8 Update test results 2016-11-14 16:47:28 +00:00
Cmdr McDonald
33a7c71fec Tweaks for restoring data from previous builds 2016-11-14 16:39:58 +00:00
Cmdr McDonald
4e0f682ad6 Use forrked browserify-zlib as it has bug fixes 2016-11-14 12:17:30 +00:00
Cmdr McDonald
4486aa2e2b Handle saved builds and old URLs 2016-11-14 11:57:39 +00:00
Cmdr McDonald
0c94c81746 Remove explicit bulkheads name from slot 2016-11-13 21:02:39 +00:00
Cmdr McDonald
5b037e3a00 Additional info if import fails 2016-11-13 17:04:57 +00:00
Cmdr McDonald
42a2b907ce Linting 2016-11-13 16:51:07 +00:00
Cmdr McDonald
a65dae1631 Various fixes; allow direct import from URL 2016-11-13 16:42:59 +00:00
Cmdr McDonald
7d4c534956 Re-enable shields for comparison 2016-11-13 13:44:00 +00:00
Cmdr McDonald
8397d3505b Rework per-module resistance calculations 2016-11-13 13:13:57 +00:00
Cmdr McDonald
9556f28ba4 Add ability to import directly from companion API output 2016-11-12 12:02:52 +00:00
Cmdr McDonald
f489257f86 Update shield cell numbers when appropriate 2016-11-11 12:30:32 +00:00
Cmdr McDonald
c96693c439 Tidy up descriptions; allow total cost to be non-integer 2016-11-11 12:19:08 +00:00
Cmdr McDonald
3d4f6d7861 Slightly friendlier modifications 2016-11-11 12:04:30 +00:00
Cmdr McDonald
606eabfec7 Fix issues with losing precision due to using decimal modification values. Validate modification information 2016-11-11 11:15:56 +00:00
Cmdr McDonald
782603727a Re-add recovery/recharge times and tweak styling 2016-11-11 00:39:14 +00:00
Cmdr McDonald
ad570534a0 Update test outputs 2016-11-11 00:21:53 +00:00
Cmdr McDonald
87e903e473 Add 'Offence summary' and 'Defence summary' components 2016-11-11 00:15:49 +00:00
Cmdr McDonald
cf6d32ea04 Enable boost display even if power distributor is disabled 2016-11-10 22:15:42 +00:00
Cmdr McDonald
5b81a0b25f Fix modification value for additive modifications 2016-11-10 15:18:05 +00:00
Cmdr McDonald
0688faac93 Added offence and defence summary 2016-11-10 00:13:56 +00:00
Cmdr McDonald
3719bb9696 Merge branch 'feature/fixes' into develop 2016-11-09 19:05:43 +00:00
Cmdr McDonald
21582d1598 Handle potentially null modifications object 2016-11-09 18:57:45 +00:00
Cmdr McDonald
9a9607fcfb Merge branch 'gienkov-pl-language' into feature/fixes 2016-11-09 16:41:19 +00:00
Cmdr McDonald
8602c5667b Merge branch 'pl-language' of https://github.com/gienkov/coriolis into gienkov-pl-language 2016-11-09 16:40:43 +00:00
Cmdr McDonald
f13a987388 Lint tidy-ups 2016-11-09 16:40:22 +00:00
Cmdr McDonald
38eaebefc0 Fix import and export of ships with modifications, bump schema version to 4 2016-11-09 16:32:11 +00:00
Cmdr McDonald
c1bc514e6b Remove swapfile 2016-11-08 09:29:32 +00:00
Cmdr McDonald
42e98fd015 Fix tooltip DPS 2016-11-08 09:25:01 +00:00
Cmdr McDonald
5f0b851de7 Take modifications in to account when deciding whether to issue a
warning on a standard module.
Fix for #16.
2016-11-08 09:05:54 +00:00
Cmdr McDonald
616ed0bf10 Show modification icon for modified modules
Fix for #14
2016-11-07 17:11:39 +00:00
Cmdr McDonald
108ab3b1ee Update DPS/HPS/EPS in real-time as modifiers change 2016-11-07 10:15:20 +00:00
Cmdr McDonald
04caef9613 Merge branch 'release/2.2.1' into develop 2016-11-05 14:39:28 +00:00
Cmdr McDonald
cae7edbe01 Merge branch 'release/2.2.1' 2016-11-05 14:39:21 +00:00
Cmdr McDonald
2fa96fc1a5 Bump version 2016-11-05 14:37:37 +00:00
Cmdr McDonald
6b8a4d1f85 Merge branch 'feature/mods' into develop 2016-11-05 14:03:24 +00:00
Cmdr McDonald
07df44a907 Allow 3dp for ship and module discounts 2016-11-05 13:59:23 +00:00
Cmdr McDonald
be45637435 Use GZIP to decrease length of modifications URL string 2016-11-05 13:43:57 +00:00
Cmdr McDonald
87ab684ba3 Use degree symbol for jitter 2016-11-04 23:58:03 +00:00
Cmdr McDonald
916a6d5e48 Show shield boost decimal place 2016-11-04 23:00:19 +00:00
Cmdr McDonald
b99fbf07f5 Fix up name of shield boost 2016-11-04 22:34:08 +00:00
Cmdr McDonald
6d6ef2a93e Provide weights to 2dp 2016-11-04 20:53:05 +00:00
Cmdr McDonald
d08e5e2858 Fixes for serialization 2016-11-04 17:13:28 +00:00
Cmdr McDonald
c1f4a8d416 Add tooltips for icons; fix import of old-style modification strings 2016-11-04 13:12:44 +00:00
Cmdr McDonald
c17c7125e3 Tidy-ups 2016-11-04 11:34:39 +00:00
Cmdr McDonald
97fc4ce45d Fixes and tidy-ups 2016-11-04 11:31:48 +00:00
Cmdr McDonald
abfc338240 Tidy-ups 2016-11-03 16:19:13 +00:00
Cmdr McDonald
3d129946ce Updates module diff details 2016-11-03 12:23:25 +00:00
Cmdr McDonald
2aab31d317 Fix typo 2016-11-03 11:33:00 +00:00
Cmdr McDonald
f8ff9a6a87 Add more fillouts 2016-11-03 11:29:41 +00:00
Cmdr McDonald
6727c2fc13 Move to pure damage/distdraw/thermload numbers 2016-11-03 10:52:54 +00:00
Cmdr McDonald
ba1f11a88b Updates for shield strength 2016-11-02 14:14:15 +00:00
Cmdr McDonald
c88e4369c8 Update to handle armour 2016-11-02 13:32:49 +00:00
Cmdr McDonald
c490f97c22 Use modifiers when calculating regen rates 2016-11-02 11:11:45 +00:00
Cmdr McDonald
7c71555384 Added facing limit and range for fsdi 2016-11-02 11:03:29 +00:00
Cmdr McDonald
35538f971a Tidy-ups 2016-11-02 10:47:10 +00:00
Cmdr McDonald
b3c82ac2de Added (non-functional) resistances 2016-11-02 10:35:55 +00:00
Cmdr McDonald
ff0d8dccea Handle bulkheads 2016-11-02 09:54:52 +00:00
Cmdr McDonald
06841b1485 Change arc to facinglimit 2016-11-01 12:22:03 +00:00
Cmdr McDonald
f55448c0a8 Ensure that fuel tank mass is displayed 2016-11-01 07:24:38 +00:00
Cmdr McDonald
258701c377 Separate modification slider in to its own component 2016-10-31 23:19:22 +00:00
Cmdr McDonald
1a0f05511b Various bug fixes 2016-10-31 21:08:15 +00:00
Cmdr McDonald
3ec9679893 Tidy up diff 2016-10-31 12:13:03 +00:00
Cmdr McDonald
e5cc3e269e Updates for modifications 2016-10-31 11:22:36 +00:00
Cmdr McDonald
4b14f617ec Changes for mods 2016-10-30 20:54:36 +00:00
Cmdr McDonald
0df051e40f Move more values to functions over direct variable access 2016-10-29 23:32:52 +01:00
Cmdr McDonald
2b464f34e5 Use react-number-editor for modifications 2016-10-29 14:14:57 +01:00
Cmdr McDonald
7a33bd64f8 Updates to modifications menu 2016-10-28 01:46:21 +01:00
Cmdr McDonald
3656c7a18f Update naming to match Elite names 2016-10-27 15:11:23 +01:00
Cmdr McDonald
3114852c63 Updates for mods UI 2016-10-27 13:26:09 +01:00
Cmdr McDonald
45337913ba Make modifications icon clickable 2016-10-25 13:36:46 +01:00
Cmdr McDonald
adc5d1c039 Only show modifications icon if the module has valid modifications. Use no more than two decimal places for modification values. 2016-10-24 18:40:12 +01:00
Cmdr McDonald
183f22c223 Updates to internal representation of modification information. Temporary power usage slider with standard modules for testing 2016-10-24 00:38:12 +01:00
Cmdr McDonald
43eb4935e6 Merge branch 'master' into develop 2016-10-22 21:03:35 +01:00
Cmdr McDonald
2c45011664 Rework modules to be individual objects rather than references to templates 2016-10-22 09:47:43 +01:00
Cmdr McDonald
f29f3f4f8f Merge branch 'release/2.2.0' into develop 2016-10-22 09:31:08 +01:00
Cmdr McDonald
c347336055 Merge branch 'release/2.2.0' 2016-10-22 09:31:05 +01:00
Cmdr McDonald
cd87184169 Apply discounts when ship updates 2016-10-17 19:30:58 +01:00
Cmdr McDonald
09895852c3 Fixes for tests 2016-10-17 15:12:05 +01:00
Cmdr McDonald
32759d3e0e Fix lint issues 2016-10-17 14:33:24 +01:00
Cmdr McDonald
965134fc02 Fix serializer for named standard modules - #2 2016-10-17 14:33:12 +01:00
Cmdr McDonald
7da5360349 Bumped release number 2016-10-16 13:50:45 +01:00
Cmdr McDonald
bf0701340c Use UTF-8 glyph for infinity rather than custom SVG 2016-10-15 09:10:40 +01:00
Cmdr McDonald
a3a490f442 Merge branch 'feature/cabins' into develop 2016-10-14 17:12:57 +01:00
Cmdr McDonald
33b22b8280 Use correct i18n formatting methods. Update note to 2.2 2016-10-14 14:53:20 +01:00
Cmdr McDonald
e6278858b9 Added drag and drop logic for special compartments 2016-10-14 12:09:54 +01:00
Cmdr McDonald
ed7a9ef037 Display new-style hardpoint information 2016-10-14 01:04:05 +01:00
Cmdr McDonald
9c42179617 Handle fighter bays and luxury passenger cabins 2016-10-12 11:48:42 +01:00
Grzegorz
5634fbd568 pl language 2016-10-10 17:47:37 +02:00
Grzegorz
7d99d59790 pl option in menu 2016-10-10 17:39:17 +02:00
Paul Heisig
5f75c9d32b final changes before coriolis.edcd.io launch 2016-10-09 20:49:29 +02:00
Jonathan Harris
fe1d16415e Update links 2016-10-09 11:35:59 +01:00
Jonathan Harris
ed8cbb403e Point badges to this fork 2016-10-09 11:24:17 +01:00
Jonathan Harris
58182bddce Pass lint 2016-10-09 11:21:06 +01:00
Jonathan Harris
771ba7e692 Remove debugging statement 2016-10-09 11:18:36 +01:00
Paul Heisig
ed0d4ddc48 making coriolis enhanced deploy ready... 2016-10-07 21:26:27 +02:00
Paul Heisig
266c63bc0c test commit 2016-10-07 19:21:01 +02:00
Colin McLeod
ebcc92f7d4 Allow external webpack hotloading 2016-06-18 19:59:29 -07:00
Colin McLeod
a1f280cf62 Allow middle click, Fixes #173 2016-06-18 19:59:09 -07:00
Colin McLeod
0306167e6e Cleanup wepback devserver output 2016-06-18 19:47:20 -07:00
Colin McLeod
162136194a Add build name to title, Fixes #171 2016-06-18 19:46:20 -07:00
Colin McLeod
3600a4862f Merge pull request #172 from w0rm49/translation-fixes
fix some typos and errors in russian translation
2016-06-15 20:19:50 -07:00
w0rm49
65fdad64b2 fix some typos and errors in russian translation 2016-06-08 16:23:10 +03:00
157 changed files with 49669 additions and 11939 deletions

View File

@@ -1,3 +1,34 @@
{ {
"presets": ["es2015", "react", "stage-0"] "presets": [
} ["@babel/preset-env", {"modules": "commonjs"}],
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-import-meta",
["@babel/plugin-proposal-class-properties", { "loose": true }],
"@babel/plugin-proposal-json-strings",
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
"@babel/plugin-proposal-function-sent",
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-throw-expressions",
"@babel/plugin-proposal-export-default-from",
"@babel/plugin-proposal-logical-assignment-operators",
"@babel/plugin-proposal-optional-chaining",
[
"@babel/plugin-proposal-pipeline-operator",
{
"proposal": "minimal"
}
],
"@babel/plugin-proposal-nullish-coalescing-operator",
"@babel/plugin-proposal-do-expressions",
"@babel/plugin-proposal-function-bind"
]
}

21
.editorconfig Normal file
View File

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

103
.esdoc.json Normal file
View File

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

View File

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

7
.gitignore vendored
View File

@@ -3,3 +3,10 @@ build
.DS_Store .DS_Store
*.log *.log
nginx.pid nginx.pid
.idea
/bin
env
*.swp
.project
.vscode/
docs/

View File

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

349
ChangeLog.md Normal file
View File

@@ -0,0 +1,349 @@
#2.5.1
* Passenger count on main page
* AX Modules
* Engineering fixes
* Use coriolis-data 2.5.1
#2.5.0
* willyb321 and myself have conquered engineering. Mainly him though...
* Use coriolis-data 2.5.0
#2.4.2
Lots of kind people have helped out for this release! Check out the PR history!
* Uses coriolis-data update:
* Fixes issues with repair limpets
* Adds requirement data
* Adds requirements panel
* Adds comma formatting to tooltip numbers
#2.4.1
* Small patches and changes
#2.4.0
* Changed compression library to Pako
* Use coriolis-data 2.4.0
* Repair Limpets added
#2.3.7
* Fixed Travis test issues
* Bumped NodeJS version to provide better compatability and support
* Added updated German Translation
* Fixed issues with Safari
* Use coriolis-data 2.3.7
* Fixed Orca mass-lock
#2.3.6
* Update miner role to provide better defaults
* Fix issue where torpedo special effects were not showing
* Fix typo causing long range blueprint to not modify shot speed in some circumstances
* Fix for Spanish translation of Chaff Launcher (thanks to DamonFstr)
* Update for Russian translation (thanks to LeeNTien)
* Use coriolis-data 2.3.6:
* Add shotspeed modifier to cannon/multi-cannon/fragment cannon
#2.3.5
* Ensure that hidden blueprint effects are applied when a blueprint is selected
* Handle display when summary values show thrusters disabled but current mass keeps them enabled
* Added updated German translations (thanks to @sweisgerber-dev)
* Power state (enabled and priority) now follows modules when they are swapped or copied
* Grey out modules that are powered off to provide a clearer visual indication
* Use coriolis-data 2.3.5:
* Fix list of available blueprints for Point Defence
* Fix integrity values for class 6 power plants
* Add shot speed for long range weapon
* Fix components for dirty drive grade 3
* Update values for Cytoscrambler
#2.3.4
* Fix crash when removing the special effect from a module
* Ensure comparisons with saved stock ships work correctly
* Add 'Racer' role
* Tidy up shipyard page; remove units from data columns and re-order for legibility
* Allow basic drag/drop functionality in Edge/Internet Explorer 11 browser
* Provide separate special effects for dumbfire and seeker missiles
* Include special effect modifiers in blueprint tooltip
* Use coriolis-data 2.3.4:
* Add missing Long Range blueprint to multi-cannon
* Fix values for thermal load of focused weapon grade 4
* Fix internal module information for power plant blueprints
* Add 'FSD Interrupt' special to dumbfire missile racks; this module now has `specials_S` and `specials_D` keys for specials to differentiate
#2.3.3
* Remove unused blueprint when hitting reset
* Add 'purchase module' external link to EDDB for refit items
* Use coriolis-data 2.3.3:
* Add Felicity Farseer to list of engineers that supply sensor and detailed surface scanner modifications
#2.3.2
* Use scan range for DSS rather than scan time
* Fix companion API import of Dolphin
* Use coriolis-data 2.3.2:
* Separate scan time and scan range
* Add Frontier IDs for new items in 2.3
* Update ownership of module blueprints for sensors and scanners
* Update railgun penetration
#2.3.0
* Make scan time visible on scanners where available
* Update power distributor able-to-boost calculation to take fractional MJ values in to account
* Revert to floating header due to issues on iOS
* Fix issue where new module added to a slot did not reset its enabled status
* Show integrity value for relevant modules
* Reset old modification values when a new roll is applied
* Fix issue with miner role where refinery would not be present in ships with class 5 slots but no class 4
* Ensure that boost value is set correctly when modifications to power distributor enable/disable boost
* Ensure that hull reinforcement modifications take the inherent resistance in to account when calculating modification percentages
* Add tooltip for blueprints providing details of the features they alter, the components required for the blueprint and the engineer(s) who cam craft them
* Use opponent's saved pips if available
* Ignore rounds per shot for EPS and HPS calculations; it's already factored in to the numbers
* Ensure that clip size modification imports result in whole numbers
* Rework of separate offence/defence/movement sections to a unified interface
* Use cargo hatch information on import if available
* Additional information of power distributor pips, boost, cargo and fuel loads added to build
* Additional information of opponent and engagement range added to build
* Reworking of offence, defence and movement information in to separate tabs as part of the outfitting screen:
* Power and costs section provides the existing 'Power' and 'Costs' sections
* Profiles section provides a number of graphs that show how various components of the build (top speed, sustained DPS against opponent's shields and armour etc) are affected by mass, range, etc.
* Offence section provides details of your build's damage distribution and per-weapon effectiveness. It also gives summary information for how long it will take for your build to wear down your opponent's shields and armour
* Defence section provides details of your build's defences against your selected opponent. It provides details of the effectiveness of your resistances of both shields and armour, and effective strength of each as a result. It also provides key metrics around shield longevity and recovery times, as well as module protection
* Fix power band marker to show safe power limit at 40% rather than 50%
* Restyle blueprint list to improve consistency with similar menus
* Use coriolis-data 2.3.0:
* Add Dolphin
* Add turreted mining lasers
* Add long range / wide angle / fast scan scanner blueprints
* Fix EDDB IDs for class 5 and 7 fighter hangars for correct shopping list
* Fix cost for rocket-propelled FSD disruptor
* Add module names for blueprints
* Fix erroneous value for grade 5 kinetic shield booster
* Add missing integrity values for some modules
* Update module reinforcement package integrity
* Update specs of Beluga as per 2.3
* Update specs of Asp Scout as per 2.3
* Update specs of Diamondback Explorer as per 2.3
* Add ED ID for Rocket Propelled FSD Disruptor
* Fix ED name for target lock breaker special
* Update scan range and angle information for sensors
* Tidy up shield cell bank information to allow for accurate calculations with modifications
* Update mine launcher stats
* Add appropriate engineers to per-module blueprint information
#2.2.19
* Power management panel now displays modules in descending order of power usage by default
* Shot speed can no longer be modified directly. Its value is derived from the range modifier for Long Range and Focused modifications
* Ensure that jump range chart updates when fuel slider is changed
* Add 'Engine profile' and 'FSD profile' charts. These show how your maximum speed/jump range will alter as you alter the mass of your build
* Use coriolis-data 2.2.19:
* Remove shot speed modification - it is directly tied to range
* Fix incorrect minimal mass for 3C bi-weave shield generator
#2.2.18
* Change methodology for calculating explorer role; can result in lighter builds
* Tidy up layout for module selection and lay everything out in a consistent best-to-worst for both class and grade
* Make integrity for module reinforcement packages visible
* Clean up breakpoints for modules in available modules list; stops 7- or 8- module long lines
* Add damager/range graphs to damage dealt
* Reorder panels
* Use coriolis-data 2.2.18:
* Correct lower efficiency value to be better, not worse
#2.2.17
* Use in-game terminology for shield generator optmul and optmass items
* Add crew to shipyard and outfitting page information
* Use coriolis-data 2.2.17:
* Add mass as potential SCB modification
* Fix mining laser statistics
* Remove non-existent grade 4 and 5 wake scanner modifications
* Add number of crew for each ship
#2.2.16
* Fix 'Extreme' blueprint roll where some incorrect ranges were chosen
* Use coriolis-data 2.2.16:
* Fix incorrect thermal load modifiers for dirty drives
* Provide explicit information about if values are higher numeric value == better or not
#2.2.15
* Ensure that standard slots are repainted when any component changes
* Reload page if Safari throws a security error
* Handle import of ships with incorrectly-sized slots
* Add 'Extreme' blueprint roll: best beneficial and worst detrimental outcome (in place of 'Average' roll)
* Display information about Microsoft browser issues when an import fails
* Add 'purchase this build' icon link to EDDB
* Add 'miner' and 'shielded miner' ship roles
* Use coriolis-data 2.2.15:
* Fix location of initial cargo rack for Vulture
* Fix broken regeneration rate for 6B shield generators
* Tidy up breach damage values
#2.2.14
* Ensure that jitter is shown correctly when the result of a special effect
* Use restyled blueprint information
* Use the ship name (if available) rather than the ship model for the window title
* Use coriolis-data 2.2.14:
* Alter blueprint structure to combine components and features
* Make hidden value of modifications its own attribute
* Fix incorrect ED ID for class 6 passenger cabins
#2.2.13
* Add 'time to drain' summary value. This is the time to drain the WEP capacitor if firing all enabled weapons
* Do not include utility slot DPS/EPS/HPS in summary information
* Ensure that auto loader special shows in the tooltip
* Ensure that ship mass is recalculated when appropriate
* Use coriolis-data 2.2.13:
* Add plasma slug special effect for plasma accelerator
* Tweak hull costs of ships
#2.2.12
* Tidy up old references to coriolis.io
* Add ability to add and remove special effects to weapon modifications
* Add weapon engineering information to Damage Dealt section
* Change shortcut for link from ctrl-l to ctrl-o to avoid clash with location bar
* Only show one of power generation or draw in tooltips, according to module
* Use coriolis-data 2.2.12:
* Add special effects for each blueprint
* Add IDs for most Powerplay modules
#2.2.11
* Add help system and initial help file
* Make absolute damage visible
* Add 'average' roll for blueprints
* Update spacing for movement summary to make it more readable
* Provide damage dealt statistics for both shields and hull
* Damage dealt panel only shows enabled weapons
* Add engagement range to damage received panel
* Handle burst rate of fire as an absolute number rather than a perentage modification
* Ensure that clip values are always rounded up
* Ensure that focused weapon mod uses range modifier to increase falloff as well
* Use coriolis-data 2.2.11:
* Remove non-existent chaff launcher capacity blueprint grades
* Fix incorrect values for charge enhanced power distributor
* Remove incorrect AFMU blueprints
* Correct fragment cannon Double Shot blueprint information
* Correct Focused weapon blueprint information
#2.2.10
* Fix detailed export of module reinforcement packages
* Use damagedist for exact breakdown of weapons that have more than one type of damage
* Use new-style modification validity data
* Provide ability to select engineering blueprint and roll sample values for them
* Use coriolis-data 2.2.10:
* Fix incorrect base shield values for Cutter and Corvette
* Update weapons to have %-based damage distributions
* Remove power draw for detailed surface scanner - although shown in outfitting it is not part of active power
* Fix incorrect names for lightweight and kinetic armour
* Add engineering blueprints
#2.2.9
* Use SSL-enabled server for shortlinks
* Add falloff for weapons
* Use falloff when calculating weapon effectiveness in damage dealt
* Add engagement range slider to 'Damage Dealt' section to allow user to see change in weapon effectiveness with range
* Use better DPE calculation methodology
* Add total DPS and effectiveness information to 'Damage Dealt' section
* Use coriolis-data 2.2.9:
* Add falloff metric for weapons
* Add falloff from range modification
#2.2.8
* Fix issue where filling all internals with cargo racks would include restricted slots
* Use coriolis-data 2.2.8:
* Set military slot of Viper Mk IV to class 3; was incorrectly set as class 2
* Update base regeneration rate of prismatic shield generators to values in 2.2.03
* Update specials with information in 2.2.03
#2.2.7
* Fix resistance diminishing return calculations
* Do not allow -100% to be entered as a modification value
#2.2.6
* Add pitch/roll/yaw information
* Use combination of pitch, roll and yaw to provide a more useful agility metric
* Add movement summary to outfitting page
* Add standard internal class sizes to shipyard page
* Fix issue when importing Viper Mk IV
* Ensure ordering of all types of modules (standard, internal, utilities) is consistent
* Add rebuilds per bay information for fighter hangars
* Add ability to show military compartments
* Show module reinforcement package results in defence summary
* Use separate speed/rotation/acceleration multipliers for thrusters if available
* Obey restricted slot rules when adding all for internal slots
* Version URLs to handle changes to ship specifications over time
* Do not include disabled shield boosters in calculations
* Add 'Damage dealt' section
* Add 'Damage received' section
* Add 'Piercing' information to hardpoints
* Add 'Hardness' information to ship summary
* Add module copy functionality - drag module whilst holding 'alt' to copy
* Add base resistances to defence summary tooltip
* Update shield recovery/regeneration calculations
* Pin menu to top of page
* Switch to custom shortlink method to avoid google length limitations
* Ensure that information is not lost on narrow screens
* Do not lose ship selector selection on narrow screens
* Reinstate jump range graph
* Use coriolis-data 2.2.6:
* Update weapons with changed values for 2.2.03
* Add individual pitch/roll/yaw statistics for each ship
* Remove old and meaningless agility stat
* Use sane order for multi-module JSON - coriolis can re-order as it sees fit when displaying modules
* Fix cost of fighter hangars
* Update Powerplay weapons with current statistics
* Add separate min/opt/max multipliers for enhanced thrusters for speed, acceleration and rotation
* Add module reinforcement packages
* Add military compartments
* Fix missing damage value for 2B dumbfires
* Update shield recharge rates
* Reduce hull mass of Viper to 50T
* Fix incorrect optimal mass value for 8A thrusters
* Add power draw for detailed surface scanner
#2.2.5
* Calculate rate of fire for multi-burst weapons
* Add note to disable ghostery in error situation
* Use coriolis-data 2.2.5:
* Fix incorrect ID for emissive munitions special
* Fix rate of fire for burst lasers
* Add fragment cannon modifications
* Fix internal name of dazzle shell
#2.2.4
* Add shortlink for outfitting page
* Use coriolis-data 2.2.4:
* Fix incorrect ID for class 5 luxury passenger cabin
* Add damage type modifier
* Change modifications from simple strings to objects, to allow more data-driven behaviour
* Add special effects
* Modification tooltip now shows special effect
#2.2.3
* Fix hull boost calculation - now shows correct % modifier and total armour
* Fix import of DiamondBack - can now be imported
* Fix import of Beluga - can now be imported
* Use coriolis-data 2.2.3:
* Fix mismatch between class 5 and class 7 fighter hangars - now shows correct module
* Add details for concordant sequence special effect - now shows correct damage
* Fix details for thermal shock special effect - now shows correct damage
* Add engineer blueprints
* Modification tooltip now shows name and grade of modifications for imported builds
* Retain import URL unless user changes the build - allows future updates of Coriolis to take advantage of additional build information
#2.2.2
* Update DPS/HPS/EPS in real-time as modifiers change
* Use coriolis-data 2.2.2:
* Add distributor draw modifier to shield generators
* Remove modifiers for sensors
* Add initial loadout passenger cabins for Beluga
* Add initial loadout passenger cabins for Orca
* Update costs and initial loadouts for Keelback and Type-7
* Add resistances for hull reinforcement packages
* Added modifier actions to create modifications from raw data
* Show modification icon for modified modules
* Take modifications in to account when deciding whether to issue a warning on a standard module
* Fix hardpoint comparison DPS number when selecting an alternate module
* Ensure that retrofit tab only shows changed modules
* Fix import and export of ships with modifications, bump schema version to 4
* Enable boost display even if power distributor is disabled
* Calculate breakdown of ship offensive and defensive stats
* Add 'Offence summary' and 'Defence summary' components
* Add ability to import from companion API output through import feature
* Add ability to import from companion API output through URL

24
LICENSE.md Normal file
View File

@@ -0,0 +1,24 @@
All Data and [associated JSON](https://github.com/EDCD/coriolis-data) files are intellectual property and copyright of Frontier Developments plc ('Frontier', 'Frontier Developments') and are subject to their
[terms and conditions](https://www.frontierstore.net/terms-and-conditions/).
The code (Javascript, CSS, HTML, and SVG files only) specificially for Coriolis.io is released under the MIT License.
Copyright (c) 2015 Coriolis.io, Colin McLeod
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software (Javascript, CSS, HTML, and SVG files only), and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,5 +1,4 @@
![Latest Release](https://img.shields.io/github/release/cmmcleod/coriolis.svg) [![Build Status](https://travis-ci.org/cmmcleod/coriolis.svg?branch=master)](https://travis-ci.org/cmmcleod/coriolis) [![Tasks in Ready](https://badge.waffle.io/cmmcleod/coriolis.png?label=ready&title=Ready)](https://waffle.io/cmmcleod/coriolis) [![Tasks in Progress](https://badge.waffle.io/cmmcleod/coriolis.svg?label=in%20progress&title=In%20Progress)](http://waffle.io/cmmcleod/coriolis) [![Chat to us on HipChat](https://img.shields.io/badge/HipChat-Coriolis-white.svg?style=social)](https://www.hipchat.com/gfYQiZcmy) [![Chat to us on Discord](https://img.shields.io/badge/Discord-EDCD%20%23coriolis-blue.svg?style=social)](https://discord.gg/0uwCh6R62aPRjk9w)
## About ## About
@@ -9,47 +8,41 @@ Coriolis was created using assets and imagery from Elite: Dangerous, with the pe
## Contributing ## Contributing
Please [submit issues](https://github.com/cmmcleod/coriolis/issues), or better yet [pull requests](https://github.com/cmmcleod/coriolis/pulls) for any corrections or additions to the database or the code. - [Submit issues](https://github.com/EDCD/coriolis/issues)
- [Submit pull requests](https://github.com/EDCD/coriolis/pulls) targetting `develop` branch
### Feature Requests, Suggestions & Bugs - Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
Chat to us on [HipChat](https://www.hipchat.com/gfYQiZcmy)!
All such requests are managed and tracked through [issues](https://github.com/cmmcleod/coriolis/issues). An overview of these can be found [here](https://waffle.io/cmmcleod/coriolis).
## Development ## Development
See the [Developer's Guide](https://github.com/cmmcleod/coriolis/wiki/Developer's-Guide) in the wiki. To get a local instance of coriolis running, perform the following steps in a shell:
```sh
> git clone https://github.com/EDCD/coriolis.git
> git clone https://github.com/EDCD/coriolis-data.git
> cd ./coriolis-data
> npm install
> cd ../coriolis
> npm install
> npm start
```
You will then have a development server running on `localhost:3300`.
### Ship and Module Database ### Ship and Module Database
See the [Data wiki](https://github.com/cmmcleod/coriolis-data/wiki) for details on structure, etc. See the [Data wiki](https://github.com/cmmcleod/coriolis-data/wiki) for details on structure, etc.
## 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/cmmcleod/coriolis-data) files are intellectual property and copyright of Frontier Developments plc ('Frontier', 'Frontier Developments') and are subject to their ```sh
[terms and conditions](https://www.frontierstore.net/terms-and-conditions/). > 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 After this you need to serve the files in some manner.
Either configure your webserver to make the actual `build/` directory
Permission is hereby granted, free of charge, to any person obtaining a copy visible on the web, or alternatively copy it to somewhere to serve it
of this software (Javascript, CSS, HTML, and SVG files only), and associated documentation files (the "Software"), to deal from.
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,289 +0,0 @@
{
"$schema": "http://cdn.coriolis.io/schemas/ship-loadout/3.json#",
"name": "Test My Ship",
"ship": "Anaconda",
"references": [
{
"name": "Coriolis.io",
"url": "http://localhost:3300/outfit/anaconda/48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA?bn=Test%20My%20Ship",
"old-code": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA",
"code": "4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA",
"shipId": "anaconda"
}
],
"components": {
"standard": {
"bulkheads": "Reactive Surface Composite",
"cargoHatch": {
"enabled": false,
"priority": 5
},
"powerPlant": {
"class": 8,
"rating": "A",
"enabled": true,
"priority": 1
},
"thrusters": {
"class": 6,
"rating": "A",
"enabled": true,
"priority": 1
},
"frameShiftDrive": {
"class": 6,
"rating": "A",
"enabled": true,
"priority": 3
},
"lifeSupport": {
"class": 5,
"rating": "A",
"enabled": true,
"priority": 1
},
"powerDistributor": {
"class": 8,
"rating": "A",
"enabled": true,
"priority": 1
},
"sensors": {
"class": 8,
"rating": "A",
"enabled": true,
"priority": 1
},
"fuelTank": {
"class": 5,
"rating": "C",
"enabled": true,
"priority": 1
}
},
"hardpoints": [
{
"class": 4,
"rating": "A",
"enabled": true,
"priority": 2,
"group": "Plasma Accelerator",
"mount": "Fixed"
},
{
"class": 3,
"rating": "D",
"enabled": true,
"priority": 2,
"group": "Beam Laser",
"mount": "Turret"
},
{
"class": 3,
"rating": "D",
"enabled": true,
"priority": 2,
"group": "Beam Laser",
"mount": "Turret"
},
{
"class": 3,
"rating": "D",
"enabled": true,
"priority": 2,
"group": "Beam Laser",
"mount": "Turret"
},
{
"class": 2,
"rating": "E",
"enabled": true,
"priority": 2,
"group": "Cannon",
"mount": "Turret"
},
{
"class": 2,
"rating": "E",
"enabled": true,
"priority": 2,
"group": "Cannon",
"mount": "Turret"
},
{
"class": 1,
"rating": "F",
"enabled": true,
"priority": 2,
"group": "Beam Laser",
"mount": "Turret"
},
{
"class": 1,
"rating": "F",
"enabled": true,
"priority": 2,
"group": "Beam Laser",
"mount": "Turret"
}
],
"utility": [
{
"class": 0,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Shield Booster"
},
{
"class": 0,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Shield Booster"
},
null,
{
"class": 0,
"rating": "C",
"enabled": true,
"priority": 2,
"group": "Kill Warrant Scanner"
},
{
"class": 0,
"rating": "C",
"enabled": true,
"priority": 2,
"group": "Cargo Scanner"
},
{
"class": 0,
"rating": "F",
"enabled": false,
"priority": 1,
"group": "Countermeasure",
"name": "Electronic Countermeasure"
},
{
"class": 0,
"rating": "I",
"enabled": true,
"priority": 1,
"group": "Countermeasure",
"name": "Chaff Launcher"
},
{
"class": 0,
"rating": "I",
"enabled": true,
"priority": 2,
"group": "Countermeasure",
"name": "Point Defence"
}
],
"internal": [
{
"class": 7,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Shield Generator"
},
{
"class": 6,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Shield Cell Bank"
},
{
"class": 6,
"rating": "E",
"enabled": true,
"priority": 1,
"group": "Cargo Rack"
},
{
"class": 5,
"rating": "D",
"enabled": true,
"priority": 1,
"group": "Hull Reinforcement Package"
},
{
"class": 5,
"rating": "E",
"enabled": true,
"priority": 1,
"group": "Cargo Rack"
},
null,
null,
{
"class": 4,
"rating": "E",
"enabled": true,
"priority": 1,
"group": "Cargo Rack"
},
{
"class": 4,
"rating": "E",
"enabled": true,
"priority": 1,
"group": "Cargo Rack"
},
{
"class": 4,
"rating": "A",
"enabled": true,
"priority": 3,
"group": "Fuel Scoop"
},
{
"class": 2,
"rating": "A",
"enabled": true,
"priority": 3,
"group": "Frame Shift Drive Interdictor"
}
]
},
"stats": {
"class": 3,
"hullCost": 141889930,
"speed": 180,
"topSpeed": 186.5,
"boost": 240,
"boostEnergy": 29,
"topBoost": 248.66,
"agility": 2,
"baseShieldStrength": 350,
"baseArmour": 945,
"hullMass": 400,
"masslock": 23,
"pipSpeed": 0.14,
"moduleCostMultiplier": 1,
"fuelCapacity": 32,
"cargoCapacity": 128,
"ladenMass": 1339.2,
"armour": 2228,
"armourAdded": 390,
"armourMultiplier": 1.95,
"shieldMultiplier": 1.4,
"totalCost": 882362060,
"unladenMass": 1179.2,
"totalDps": 29,
"powerAvailable": 36,
"powerRetracted": 23.33,
"powerDeployed": 34.76,
"unladenRange": 18.49,
"fullTankRange": 18.12,
"ladenRange": 16.39,
"unladenFastestRange": 73.21,
"ladenFastestRange": 66.15,
"maxJumpCount": 4,
"shieldStrength": 833
}
}

View File

@@ -1,22 +0,0 @@
[
{
"buildText": "[Imaginary Ship]\nbla bla",
"errorMsg": "No such ship found: \"Imaginary Ship\""
},
{
"buildText": "[Viper]\nS: 1F/F Pulse Laser\nsome un-parseable nonsense\nS: 1F/F Pulse Laser\n",
"errorMsg": "Error parsing: \"some un-parseable nonsense\""
},
{
"buildText": "[Sidewinder]\nS: 2F/F Pulse Laser\nS: 1F/F Pulse Laser\n",
"errorMsg": "2F Pulse Laser exceeds slot size: \"S: 2F/F Pulse Laser\""
},
{
"buildText": "[Sidewinder]\nL: 2F/F Pulse Laser\nS: 1F/F Pulse Laser\n",
"errorMsg": "No hardpoint slot available for: \"L: 2F/F Pulse Laser\""
},
{
"buildText": "[Sidewinder]\nS: 1F/F Magic Thing\nS: 1F/F Pulse Laser\n",
"errorMsg": "Unknown component: \"S: 1F/F Magic Thing\""
}
]

View File

@@ -1,32 +0,0 @@
[
{
"shipId": "anaconda",
"buildName": "Imported Anaconda",
"buildCode": "0pyttFolodDsyf5------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=",
"buildText": "[Anaconda]\nS: 1F/F Pulse Laser\nS: 1F/F Pulse Laser\n\nBH: 1I Lightweight Alloy\nRB: 8E Power Plant\nTM: 7E Thrusters\nFH: 6E Frame Shift Drive\nEC: 5E Life Support\nPC: 8E Power Distributor\nSS: 8E Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n7: 6E Cargo Rack (Capacity: 64)\n6: 5E Cargo Rack (Capacity: 32)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 1E Basic Discovery Scanner\n2: 1E Cargo Rack (Capacity: 2)\n"
},
{
"shipId": "anaconda",
"buildName": "Imported Anaconda",
"buildCode": "0pyttFolodDsyf5------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=",
"buildText": "\n\n \t[Anaconda]\nS: 1F/F Pulse Laser\nS: 1F/F Pulse Laser\n\nBH: 1I Lightweight Alloy\nRB: 8E Power Plant\nTM: 7E Thrusters\nFH: 6E Frame Shift Drive\nEC: 5E Life Support\nPC: 8E Power Distributor\nSS: 8E Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n7: 6E Cargo Rack (Capacity: 64)\n6: 5E Cargo Rack (Capacity: 32)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 1E Basic Discovery Scanner\n2: 1E Cargo Rack (Capacity: 2)\n"
},
{
"shipId": "cobra_mk_iii",
"buildName": "Imported Cobra Mk III",
"buildCode": "0patcFeldd5sdf41712222503040202490f242h.Iw1-kA==.Aw1-kA==",
"buildText": "[Cobra Mk III]\nM: 1F/F Pulse Laser\nM: 1G/G Burst Laser\nS: 1E/T Fragment Cannon\nS: 1G/T Multi-cannon\nU: 0I Point Defence\nU: 0A Shield Booster\n\nBH: 1I Lightweight Alloy\nRB: 4A Power Plant\nTM: 4C Thrusters\nFH: 4E Frame Shift Drive\nEC: 3D Life Support\nPC: 2A Power Distributor\nSS: 3D Sensors\nFS: 4C Fuel Tank (Capacity: 16)\n\n4: 3E Cargo Rack (Capacity: 8)\n4: 3E Cargo Rack (Capacity: 8)\n4: 4E Shield Generator\n2: 2C Auto Field-Maintenance Unit\n2: 1E Standard Docking Computer\n2: 1E Basic Discovery Scanner\n---\nShield: 112.29 MJ\nPower : 10.45 MW retracted (67%)\n 12.16 MW deployed (78%)\n 15.60 MW available\nCargo : 16 T\nFuel : 16 T\nMass : 235.5 T empty\n 267.5 T full\nRange : 10.69 LY unladen\n 10.05 LY laden\nPrice : 2,929,040 CR\nRe-Buy: 146,452 CR @ 95% insurance\n"
},
{
"shipId": "type_9_heavy",
"buildName": "Imported Type-9 Heavy",
"buildCode": "3pftsFklkdisif57e2k2f2h110001020306054j03022f01242i.Iw18eQ==.Aw18eQ==",
"buildText": "[Type-9 Heavy]\nM: 2D/G Fragment Cannon\nM: 2I/F Mine Launcher\nM: 2B/FD Missile Rack\nS: 1I/FS Torpedo Pylon\nS: 1F/F Burst Laser\nU: 0I Chaff Launcher\nU: 0F Electronic Countermeasure\nU: 0I Heat Sink Launcher\nU: 0I Point Defence\n\nBH: 1I Mirrored Surface Composite\nRB: 5A Power Plant\nTM: 7D Thrusters\nFH: 6A Frame Shift Drive\nEC: 5A Life Support\nPC: 4D Power Distributor\nSS: 4D Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n8: 7E Cargo Rack (Capacity: 128)\n7: 6E Cargo Rack (Capacity: 64)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 3E Cargo Rack (Capacity: 8)\n4: 1C Advanced Discovery Scanner\n3: 2E Cargo Rack (Capacity: 4)\n3: 1E Standard Docking Computer\n2: 1C Detailed Surface Scanner\n"
},
{
"shipId": "vulture",
"buildName": "Imported Vulture",
"buildCode": "4patfFalddksif31e1e0e0j04044a0n532jf1.Iw19kA==.Aw19kA==",
"buildText": "[Vulture]\nL: 3E/G Pulse Laser\nL: 3E/G Pulse Laser\nU: 0A Frame Shift Wake Scanner\nU: 0A Kill Warrant Scanner\nU: 0A Shield Booster\nU: 0A Shield Booster\n\nBH: 1I Reactive Surface Composite\nRB: 4A Power Plant\nTM: 5A Thrusters\nFH: 4A Frame Shift Drive\nEC: 3D Life Support\nPC: 5A Power Distributor\nSS: 4D Sensors\nFS: 3C Fuel Tank (Capacity: 8)\n\n5: 5A Shield Generator\n4: 4A Auto Field-Maintenance Unit\n2: 2A Shield Cell Bank\n1: 1A Fuel Scoop\n1: 1C Fuel Tank (Capacity: 2)"
}
]

View File

@@ -1,50 +0,0 @@
{
"type_6_transporter": {
"Cargo": "0p0tdFal8d8s8f4-----04040303430101.Iw1-kA==.Aw1-kA==",
"Miner": "0p5tdFal8d8s8f42l2l---040403451q0101.Iw1-kA==.Aw1-kA==",
"Hopper": "0p0tdFal8d0s8f41717---030302024300-.Iw1-kA==.Aw1-kA=="
},
"type_7_transport": {
"Cargo": "0p0tiFfliddsdf5--------0505040403480101.Iw18aQ==.Aw18aQ==",
"Miner": "0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ=="
},
"federal_dropship": {
"Cargo": "0pdtiFflnddsif4-1717------05040448020201.Iw18aQ==.Aw18aQ=="
},
"asp": {
"Miner": "2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ=="
},
"imperial_clipper": {
"Cargo": "0p5tiFflndisnf4--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==",
"Dream": "2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o-.Iw18aQ==.Aw18aQ==",
"Current": "0patkFflndfskf4----------------.Iw18aQ==.Aw18aQ=="
},
"type_9_heavy": {
"Current": "0patsFklndnsif6---------0706054a0303020224.Iw18eQ==.Aw18eQ=="
},
"python": {
"Cargo": "0patnFflidsssf5---------050505040448020201.Iw18eQ==.Aw18eQ==",
"Miner": "0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.Aw18eQ==",
"Dream": "2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201.Iw18eQ==.Aw18eQ==",
"Missile": "0pttoFjljdystf52f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ=="
},
"anaconda": {
"Dream": "4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d040303326b.Iw18ZlA=.Aw18ZlA=",
"Cargo": "0patnFklndnsxf5----------------0605050504040445030301.Iw18ZlA=.Aw18ZlA=",
"Current": "0patnFklndksxf5----------------0605050504040403034524.Iw18ZlA=.Aw18ZlA=",
"Explorer": "0patnFklndksxf5--------0202------f7050505040s372f2i4524.Iw18ZlA=.Aw18ZlA=",
"Test": "4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.Iw18ZlA=.Aw18ZlA="
},
"diamondback_explorer": {
"Explorer": "0p0tdFfldddsdf5---0202--320p432i2f.Iw1-kA==.Aw1-kA=="
},
"vulture": {
"Bounty Hunter": "3patcFalddksff31e1e0404-0l4a5d27662j.Iw19kA==.Aw19kA=="
},
"fer_de_lance": {
"Attack": "2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.Aw18aQ=="
},
"eagle": {
"Figther": "4p0t5F5l3d5s5f20p0p24-40532j-.Iw1-EA==.Aw1-EA=="
}
}

View File

@@ -1,50 +0,0 @@
{
"builds": {
"type_6_transporter": {
"Cargo": "02A4D4A2D2D2D4C-----04040303430101",
"Miner": "03A4D4A2D2D2D4C2l2l---040403451q0101",
"Hopper": "02A4D4A2D1A2D4C1717---030302024300-"
},
"type_7_transport": {
"Cargo": "02A5D5A4D3D3D5C--------0505040403480101",
"Miner": "04D5D5A4D2D3D5C--2l2l----0505041v03450000"
},
"federal_dropship": {
"Cargo": "04D5D5A5D3D4D4C-1717------05040448020201"
},
"asp": {
"Miner": "25A5A5A4D4A5A5C0s0s24242l2l---04054a1q02022o27"
},
"imperial_clipper": {
"Cargo": "03A5D5A5D4D5D4C--0s0s----0605450302020101",
"Dream": "26A6A5A5D6A5A4C0v0v0s0s0404040n4k5n5d2b29292o-.AwRj4yWU1I==.CwBhCYy6YRigzLIA",
"Current": "04A6A5A5D4A5A4C----------------.AwRj4yWU1I==.CwBhCYy6YRigzLIA"
},
"type_9_heavy": {
"Current": "04A7D6A5D5D4D6C---------0706054a0303020224.AwRj4yoo.EwBhEYy6dsg="
},
"python": {
"Cargo": "04A6D5A4D6D6D5C---------050505040448020201.Iw18eQ==.Aw18eQ==",
"Miner": "06A6A5A4D6A6A5C0v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.IwBhBYy6dkCYg===",
"Dream": "27A6A5A4D7A6A5C0v0v0v27270404040m5n5n4f2d2d032t0201.Iw1+gDBxA===.EwBhEYy6e0WEA==="
},
"anaconda": {
"Dream": "48A7A6A5D8A8A5C2c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d040303326b.AwRj4yo5dig=.MwBhCYy6du3ARiA=",
"Cargo": "04A6D6A5D5D8D5C----------------0605050504040445030301.Iw18ZlA=.Aw18ZlA=",
"Current": "04A6D6A5D5A8D5C----------------0605050504040403034524.Iw18ZlA=.Aw18ZlA=",
"Explorer": "04A6D6A5D5A8D5C--------0202------f7050505040s372f2i4524.AwRj4yVKJthA.AwhMIyungRhEA==="
},
"diamondback_explorer": {
"Explorer": "02A4D5A3D3D3D5C---0202--320p432i2f.AwRj4zTI.AwiMIypI"
},
"vulture": {
"Bounty Hunter": "34A4C4A3D5A4A3C1e1e0404-0l4a5d27662j.AwRj4y2I.MwBhBYy6wJmAjLIA"
},
"fer_de_lance": {
"Attack": "25A5C4A4D6A4A3C1r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.CwBhrSu8EZyA"
},
"eagle": {
"Figther": "42A3A3A1D2A2A2C0p0p24-40532j.AwRj49iA.AwgsIkEZigmIA==="
}
}
}

View File

@@ -1,66 +0,0 @@
{
"builds": {
"type_6_transporter": {
"Cargo": "02A4D4A2D2D2D4C-----04040303430101",
"Miner": "03A4D4A2D2D2D4C2l2l---040403451q0101",
"Hopper": "02A4D4A2D1A2D4C1717---030302024300-"
},
"type_7_transport": {
"Cargo": "02A5D5A4D3D3D5C--------0505040403480101",
"Miner": "04D5D5A4D2D3D5C--2l2l----0505041v03450000"
},
"federal_dropship": {
"Cargo": "04D5D5A5D3D4D4C-1717------05040448020201"
},
"asp": {
"Miner": "25A5A5A4D4A5A5C0s0s24242l2l---04054a1q02022o27"
},
"cobra_mk_iii": {
"Example": "24A4A4A3D3A3A4C0s0s2d2d0m0445032b2o2753.AwRj4yKA.CwBhEYyrKhmMQ==="
},
"imperial_clipper": {
"Cargo": "03A5D5A5D4D5D4C--0s0s----0605450302020101",
"Multi-purpose": "26A4A5A5D6A5A4C0v0v272704090j0h064f2c0302020101",
"Current": "05A6D5A5D6A5A4C0v0v27270404050n4m05035d29292o01.AwRj4yrI.AwhMIyuBGNiA",
"Dream": "26A6A5A5D6A5A4C0v0v0s0s04040c0n064f5d2b02022o0d.AwRj49UlmI==.AwiMIyuo"
},
"type_9_heavy": {
"Cargo": "04A6D6A5D4D4D5C---------07064f040303010201.AwRj4yoo.EwBhEYy6dsg="
},
"python": {
"Cargo": "04A6D5A4D6D6D5C---------050505044a03020201",
"Miner": "04A6D5A4D6D6D5C---2m2m----050505044d1v02022o"
},
"anaconda": {
"Dream": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404040l0b0100034k5n05050404040303326b.AwRj4yo5dig=.MwBhEYy6duwEziA=",
"Cargo": "03A7D6A5D4D8D5C----------------060505054d040403030301.AwRj4yuqg===.Aw18ZlA="
},
"diamondback_explorer": {
"Explorer": "02A4D5A3D3D3D5C-------320p432i2f.AwRj4zTI.AwiMIypI"
}
},
"comparisons": {
"Test": {
"facets": [ 9, 6, 4, 1, 3, 2 ],
"builds": [
{
"shipId": "anaconda",
"buildName": "Dream"
},
{
"shipId": "asp",
"buildName": "Miner"
},
{
"shipId": "diamondback_explorer",
"buildName": "Explorer"
}
]
}
},
"insurance": "Beta",
"discounts": [
1,
1
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,225 +0,0 @@
jest.dontMock('../src/app/stores/Persist');
jest.dontMock('../src/app/components/TranslatedComponent');
jest.dontMock('../src/app/components/ModalImport');
import React from 'react';
import ReactDOM from 'react-dom';
import TU from 'react-testutils-additions';
import Utils from './testUtils';
import { getLanguage } from '../src/app/i18n/Language';
describe('Import Modal', function() {
let MockRouter = require('../src/app/Router').default;
const Persist = require('../src/app/stores/Persist').default;
const ModalImport = require('../src/app/components/ModalImport').default;
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()
};
let modal, render, ContextProvider = Utils.createContextProvider(mockContext);
/**
* Clear saved builds, and reset React DOM
*/
function reset() {
MockRouter.go.mockClear();
Persist.deleteAll();
render = TU.renderIntoDocument(<ContextProvider><ModalImport /></ContextProvider>);
modal = TU.findRenderedComponentWithType(render, ModalImport);
}
/**
* Simulate user import text entry / paste
* @param {string} text Import text / raw data
*/
function pasteText(text) {
let textarea = TU.findRenderedDOMComponentWithTag(render, 'textarea');
TU.Simulate.change(textarea, { target: { value: text } });
}
/**
* Simulate click on Proceed button
*/
function clickProceed() {
let proceedButton = TU.findRenderedDOMComponentWithId(render, 'proceed');
TU.Simulate.click(proceedButton);
}
/**
* Simulate click on Import button
*/
function clickImport() {
let importButton = TU.findRenderedDOMComponentWithId(render, 'import');
TU.Simulate.click(importButton);
}
describe('Import Backup', function() {
beforeEach(reset);
it('imports a valid backup', function() {
let importData = require('./fixtures/valid-backup');
let importString = JSON.stringify(importData);
expect(modal.state.importValid).toEqual(false);
expect(modal.state.errorMsg).toEqual(null);
pasteText(importString);
expect(modal.state.importValid).toBe(true);
expect(modal.state.errorMsg).toEqual(null);
expect(modal.state.builds).toEqual(importData.builds);
expect(modal.state.comparisons).toEqual(importData.comparisons);
expect(modal.state.shipDiscount).toEqual(importData.discounts[0]);
expect(modal.state.moduleDiscount).toEqual(importData.discounts[1]);
expect(modal.state.insurance).toBe(importData.insurance.toLowerCase());
clickProceed();
expect(modal.state.processed).toBe(true);
expect(modal.state.errorMsg).toEqual(null);
clickImport();
expect(Persist.getBuilds()).toEqual(importData.builds);
expect(Persist.getComparisons()).toEqual(importData.comparisons);
expect(Persist.getInsurance()).toEqual(importData.insurance.toLowerCase());
expect(Persist.getShipDiscount()).toEqual(importData.discounts[0]);
expect(Persist.getModuleDiscount()).toEqual(importData.discounts[1]);
});
it('imports an old valid backup', function() {
const importData = require('./fixtures/old-valid-export');
const importStr = JSON.stringify(importData);
pasteText(importStr);
expect(modal.state.builds).toEqual(importData.builds);
expect(modal.state.importValid).toBe(true);
expect(modal.state.errorMsg).toEqual(null);
clickProceed();
expect(modal.state.processed).toBeTruthy();
clickImport();
expect(Persist.getBuilds()).toEqual(importData.builds);
});
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
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'));
expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual('"invalid_ship" is not a valid Ship Id!');
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!');
pasteText(JSON.stringify(invalidImportData));
expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual('asp build "Miner" data is missing!');
});
});
describe('Import Detailed Build', function() {
beforeEach(reset);
it('imports a valid v3 build', function() {
const importData = require('./fixtures/anaconda-test-detailed-export-v3');
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/anaconda/4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA?bn=Test%20My%20Ship');
});
it('catches an invalid build', function() {
const importData = require('./fixtures/anaconda-test-detailed-export-v3');
pasteText(JSON.stringify(importData).replace('components', 'comps'));
expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual('Anaconda Build "Test My Ship": Invalid data');
});
});
describe('Import Detaild Builds Array', function() {
beforeEach(reset);
it('imports all builds', function() {
const importData = require('./fixtures/valid-detailed-export');
const expectedBuilds = require('./fixtures/expected-builds');
pasteText(JSON.stringify(importData));
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
clickProceed();
expect(modal.state.processed).toBeTruthy();
clickImport();
let builds = Persist.getBuilds();
for (let s in builds) {
for (let b in builds[s]) {
expect(builds[s][b]).toEqual(expectedBuilds[s][b]);
}
}
});
});
describe('Import E:D Shipyard Builds', function() {
it('imports a valid builds', function() {
const imports = require('./fixtures/ed-shipyard-import-valid');
for (let i = 0; i < imports.length; i++ ) {
reset();
let fixture = imports[i];
pasteText(fixture.buildText);
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/' + fixture.shipId + '/' + fixture.buildCode + '?bn=' + encodeURIComponent(fixture.buildName));
}
});
it('catches invalid builds', function() {
const imports = require('./fixtures/ed-shipyard-import-invalid');
for (let i = 0; i < imports.length; i++ ) {
reset();
pasteText(imports[i].buildText);
expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual(imports[i].errorMsg);
}
});
});
describe('Imports from a Comparison', function() {
it('imports a valid comparison', function() {
const importBuilds = require('./fixtures/valid-backup').builds;
Persist.deleteAll();
render = TU.renderIntoDocument(<ContextProvider><ModalImport builds={importBuilds} /></ContextProvider>);
modal = TU.findRenderedComponentWithType(render, ModalImport);
expect(modal.state.processed).toBe(true);
expect(modal.state.errorMsg).toEqual(null);
clickImport();
expect(Persist.getBuilds()).toEqual(importBuilds);
});
});
});

View File

@@ -1,143 +0,0 @@
jest.dontMock('../src/app/stores/Persist');
import React from 'react';
import ReactDOM from 'react-dom';
import TU from 'react-testutils-additions';
let origAddEventListener = window.addEventListener;
let storageListener;
let ls = {};
// Implment mock localStorage
let localStorage = {
getItem: function(key) {
return ls[key];
},
setItem: function(key, value) {
ls[key] = value;
},
removeItem: function(key) {
delete ls[key];
},
clear: function() {
ls = {};
}
}
window.addEventListener = function(eventName, listener) {
if(eventName == 'storage') {
storageListener = listener; // Keep track of latest storage listener
} else {
origAddEventListener.apply(arguments);
}
}
describe('Persist', function() {
const Persist = require('../src/app/stores/Persist').Persist;
describe('Multi tab/window', function() {
it("syncs builds", function() {
window.localStorage = localStorage;
ls = {};
let p = new Persist();
let newBuilds = {
anaconda: { test: '1234' }
};
storageListener({ key: 'builds', newValue: JSON.stringify(newBuilds) });
expect(p.getBuild('anaconda', 'test')).toBe('1234');
});
});
describe('General and Settings', function() {
it("has defaults", function() {
window.localStorage = localStorage;
ls = {};
let p = new Persist();
expect(p.getLangCode()).toBe('en');
expect(p.showTooltips()).toBe(true);
expect(p.getInsurance()).toBe('standard');
expect(p.getShipDiscount()).toBe(0);
expect(p.getModuleDiscount()).toBe(0);
expect(p.getSizeRatio()).toBe(1);
});
it("loads from localStorage correctly", function() {
window.localStorage = localStorage;
let savedData = require('./fixtures/valid-backup');
ls = {};
ls.builds = JSON.stringify(savedData.builds);
ls.NG_TRANSLATE_LANG_KEY = 'de';
ls.insurance = 'Standard';
ls.shipDiscount = 0.25;
ls.moduleDiscount = 0.15;
let p = new Persist();
expect(p.getInsurance()).toBe('standard');
expect(p.getShipDiscount()).toBe(0.25);
expect(p.getModuleDiscount()).toBe(0.15);
expect(p.getLangCode()).toEqual('de');
expect(p.getBuilds('anaconda')).toEqual(savedData.builds.anaconda);
expect(p.getBuilds('python')).toEqual(savedData.builds.python);
expect(p.getBuildsNamesFor('imperial_clipper')).toEqual(['Cargo', 'Current', 'Dream', 'Multi-purpose']);
expect(p.getBuild('type_7_transport', 'Cargo')).toEqual('02A5D5A4D3D3D5C--------0505040403480101');
});
it("uses defaults from a corrupted localStorage", function() {
window.localStorage = localStorage;
ls = {};
ls.builds = "not valid json";
ls.comparisons = "1, 3, 4";
ls.insurance = 'this insurance does not exist';
ls.shipDiscount = 'this is not a number';
ls.moduleDiscount = 10; // Way to big
let p = new Persist();
expect(p.getLangCode()).toBe('en');
expect(p.showTooltips()).toBe(true);
expect(p.getInsurance()).toBe('standard');
expect(p.getShipDiscount()).toBe(0);
expect(p.getModuleDiscount()).toBe(0);
expect(p.getBuilds()).toEqual({});
expect(p.getComparisons()).toEqual({});
});
it("works without localStorage", function() {
window.localStorage = null;
let p = new Persist();
expect(p.getLangCode()).toBe('en');
expect(p.showTooltips()).toBe(true);
expect(p.getInsurance()).toBe('standard');
expect(p.getShipDiscount()).toBe(0);
expect(p.getModuleDiscount()).toBe(0);
expect(p.getSizeRatio()).toBe(1);
p.saveBuild('anaconda', 'test', '12345');
expect(p.getBuild('anaconda', 'test')).toBe('12345');
p.deleteBuild('anaconda', 'test');
expect(p.hasBuilds()).toBe(false);
});
it("generates the backup", function() {
window.localStorage = localStorage;
let savedData = require('./fixtures/valid-backup');
ls = {};
ls.builds = JSON.stringify(savedData.builds);
ls.insurance = 'Beta';
ls.shipDiscount = 0.25;
ls.moduleDiscount = 0.15;
let p = new Persist();
let backup = p.getAll();
expect(backup.insurance).toBe('beta');
expect(backup.shipDiscount).toBe(0.25);
expect(backup.moduleDiscount).toBe(0.15);
expect(backup.builds).toEqual(savedData.builds);
expect(backup.comparisons).toEqual({});
});
});
})

View File

@@ -1,63 +0,0 @@
import Ship from '../src/app/shipyard/Ship';
import { Ships } from 'coriolis-data/dist';
import * as Serializer from '../src/app/shipyard/Serializer';
import jsen from 'jsen';
describe("Serializer", function() {
const anacondaTestExport = require.requireActual('./fixtures/anaconda-test-detailed-export-v3');
const code = anacondaTestExport.references[0].code;
const anaconda = Ships.anaconda;
const validate = jsen(require('../src/schemas/ship-loadout/3'));
describe("To Detailed Build", function() {
let testBuild = new Ship('anaconda', anaconda.properties, anaconda.slots).buildFrom(code);
let exportData = Serializer.toDetailedBuild('Test My Ship', testBuild);
it("conforms to the v3 ship-loadout schema", function() {
expect(validate(exportData)).toBe(true);
});
it("contains the correct components and stats", function() {
expect(exportData.components).toEqual(anacondaTestExport.components);
expect(exportData.stats).toEqual(anacondaTestExport.stats);
expect(exportData.ship).toEqual(anacondaTestExport.ship);
expect(exportData.name).toEqual(anacondaTestExport.name);
});
});
describe("Export Detailed Builds", function() {
const expectedExport = require('./fixtures/valid-detailed-export');
const builds = require('./fixtures/expected-builds');
const exportData = Serializer.toDetailedExport(builds);
it("conforms to the v3 ship-loadout schema", function() {
expect(exportData instanceof Array).toBe(true);
for (let detailedBuild of exportData) {
expect(validate(detailedBuild)).toBe(true);
}
});
});
describe("From Detailed Build", function() {
it("builds the ship correctly", function() {
let testBuildA = new Ship('anaconda', anaconda.properties, anaconda.slots);
testBuildA.buildFrom(code);
let testBuildB = Serializer.fromDetailedBuild(anacondaTestExport);
for(var p in testBuildB) {
if (p == 'availCS') {
continue;
}
expect(testBuildB[p]).toEqual(testBuildA[p], p + ' does not match');
}
});
});
});

View File

@@ -1,156 +0,0 @@
import Ship from '../src/app/shipyard/Ship';
import { Ships } from 'coriolis-data/dist';
import * as ModuleUtils from '../src/app/shipyard/ModuleUtils';
describe("Ship", function() {
it("can build all ships", function() {
for (let s in Ships) {
let shipData = Ships[s];
let ship = new Ship(s, shipData.properties, shipData.slots);
for (let p in shipData.properties) {
expect(ship[p]).toEqual(shipData.properties[p], s + ' property [' + p + '] does not match when built');
}
ship.buildWith(shipData.defaults);
expect(ship.totalCost).toEqual(shipData.retailCost, s + ' retail cost does not match default build cost');
expect(ship.cargoCapacity).toBeDefined(s + ' cargo');
expect(ship.priorityBands[0].retracted).toBeGreaterThan(0, s + ' priorityBands');
expect(ship.powerAvailable).toBeGreaterThan(0, s + ' powerAvailable');
expect(ship.unladenRange).toBeGreaterThan(0, s + ' unladenRange');
expect(ship.ladenRange).toBeGreaterThan(0, s + ' ladenRange');
expect(ship.fuelCapacity).toBeGreaterThan(0, s + ' fuelCapacity');
expect(ship.unladenFastestRange).toBeGreaterThan(0, s + ' unladenFastestRange');
expect(ship.ladenFastestRange).toBeGreaterThan(0, s + ' ladenFastestRange');
expect(ship.shieldStrength).toBeGreaterThan(0, s + ' shieldStrength');
expect(ship.armour).toBeGreaterThan(0, s + ' armour');
expect(ship.topSpeed).toBeGreaterThan(0, s + ' topSpeed');
}
});
it("resets and rebuilds properly", function() {
var id = 'cobra_mk_iii';
var cobra = Ships[id];
var shipA = new Ship(id, cobra.properties, cobra.slots);
var shipB = new Ship(id, cobra.properties, cobra.slots);
var testShip = new Ship(id, cobra.properties, cobra.slots);
var buildA = cobra.defaults;
var buildB = {
standard:['4A', '4A', '4A', '3D', '3A', '3A', '4C'],
hardpoints: ['0s', '0s', '2d', '2d', 0, '04'],
internal: ['45', '03', '2b', '2o', '27', '53']
};
shipA.buildWith(buildA); // Build A
shipB.buildWith(buildB);// Build B
testShip.buildWith(buildA);
for(var p in testShip) {
if (p == 'availCS') {
continue;
}
expect(testShip[p]).toEqual(shipA[p], p + ' does not match');
}
testShip.buildWith(buildB);
for(var p in testShip) {
if (p == 'availCS') {
continue;
}
expect(testShip[p]).toEqual(shipB[p], p + ' does not match');
}
testShip.buildWith(buildA);
for(var p in testShip) {
if (p == 'availCS') {
continue;
}
expect(testShip[p]).toEqual(shipA[p], p + ' does not match');
}
});
it("discounts hull and components properly", function() {
var id = 'cobra_mk_iii';
var cobra = Ships[id];
var testShip = new Ship(id, cobra.properties, cobra.slots);
testShip.buildWith(cobra.defaults);
var originalHullCost = testShip.hullCost;
var originalTotalCost = testShip.totalCost;
var discount = 0.1;
expect(testShip.m.discountedCost).toEqual(originalHullCost, 'Hull cost does not match');
testShip.applyDiscounts(discount, discount);
// Floating point errors cause miniscule decimal places which are handled in the app by rounding/formatting
expect(Math.floor(testShip.m.discountedCost)).toEqual(Math.floor(originalHullCost * (1 - discount)), 'Discounted Hull cost does not match');
expect(Math.floor(testShip.totalCost)).toEqual(Math.floor(originalTotalCost * (1 - discount)), 'Discounted Total cost does not match');
testShip.applyDiscounts(0, 0); // No discount, 100% of cost
expect(testShip.m.discountedCost).toEqual(originalHullCost, 'Hull cost does not match');
expect(testShip.totalCost).toEqual(originalTotalCost, 'Total cost does not match');
testShip.applyDiscounts(discount, 0); // Only discount hull
expect(Math.floor(testShip.m.discountedCost)).toEqual(Math.round(originalHullCost * (1 - discount)), 'Discounted Hull cost does not match');
expect(testShip.totalCost).toEqual(originalTotalCost - originalHullCost + testShip.m.discountedCost, 'Total cost does not match');
});
it("enforces a single shield generator", function() {
var id = 'anaconda';
var anacondaData = Ships[id];
var anaconda = new Ship(id, anacondaData.properties, anacondaData.slots);
anaconda.buildWith(anacondaData.defaults);
expect(anaconda.internal[2].m.grp).toEqual('sg', 'Anaconda default shield generator slot');
anaconda.use(anaconda.internal[1], ModuleUtils.internal('4j')); // 6E Shield Generator
expect(anaconda.internal[2].m).toEqual(null, 'Anaconda default shield generator slot is empty');
expect(anaconda.internal[1].m.id).toEqual('4j', 'Slot 1 should have SG 4j in it');
expect(anaconda.internal[1].m.grp).toEqual('sg','Slot 1 should have SG 4j in it');
});
it("enforces a single shield fuel scoop", function() {
var id = 'anaconda';
var anacondaData = Ships[id];
var anaconda = new Ship(id, anacondaData.properties, anacondaData.slots);
anaconda.buildWith(anacondaData.defaults);
anaconda.use(anaconda.internal[4], ModuleUtils.internal('32')); // 4A Fuel Scoop
expect(anaconda.internal[4].m.grp).toEqual('fs', 'Anaconda fuel scoop slot');
anaconda.use(anaconda.internal[3], ModuleUtils.internal('32'));
expect(anaconda.internal[4].m).toEqual(null, 'Anaconda original fuel scoop slot is empty');
expect(anaconda.internal[3].m.id).toEqual('32', 'Slot 1 should have FS 32 in it');
expect(anaconda.internal[3].m.grp).toEqual('fs','Slot 1 should have FS 32 in it');
});
it("enforces a single refinery", function() {
var id = 'anaconda';
var anacondaData = Ships[id];
var anaconda = new Ship(id, anacondaData.properties, anacondaData.slots);
anaconda.buildWith(anacondaData.defaults);
anaconda.use(anaconda.internal[4], ModuleUtils.internal('23')); // 4E Refinery
expect(anaconda.internal[4].m.grp).toEqual('rf', 'Anaconda refinery slot');
anaconda.use(anaconda.internal[3], ModuleUtils.internal('23'));
expect(anaconda.internal[4].m).toEqual(null, 'Anaconda original refinery slot is empty');
expect(anaconda.internal[3].m.id).toEqual('23', 'Slot 1 should have RF 23 in it');
expect(anaconda.internal[3].m.grp).toEqual('rf','Slot 1 should have RF 23 in it');
});
});

View File

@@ -1,24 +0,0 @@
import React from 'react';
const TestUtils = {
createContextProvider: function(context) {
var _contextTypes = {};
Object.keys(context).forEach(function(key) {
_contextTypes[key] = React.PropTypes.any;
});
return React.createClass({
displayName: 'ContextProvider',
childContextTypes: _contextTypes,
getChildContext() { return context; },
render() {
return React.Children.only(this.props.children);
}
});
}
};
export default TestUtils;

6089
d3.js vendored Normal file

File diff suppressed because it is too large Load Diff

1
d3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

10
devServer.js Normal file → Executable file
View File

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

View File

@@ -1,59 +0,0 @@
worker_processes 2;
error_log ./nginx.error.log;
worker_rlimit_nofile 8192;
pid nginx.pid;
events {
worker_connections 1024;
multi_accept on;
}
http {
access_log off;
charset UTF-8;
types {
text/html html htm shtml;
text/css css;
text/xml xml rss;
image/gif gif;
image/jpeg jpeg jpg;
application/x-javascript js;
text/plain txt;
image/png png;
image/svg+xml svg;
image/x-icon ico;
application/pdf pdf;
text/cache-manifest appcache;
}
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
server {
listen 3301;
server_name localhost;
root ./build/;
index index.html;
location ~* \.(?:manifest|appcache|html?|xml|json|css|js|map|jpg|jpeg|gif|png|ico|svg|eot|ttf|woff|woff2)$ {
expires -1;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
access_log off;
}
location / {
try_files $uri $uri/ /index.html =404;
}
}
}

30544
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,95 +1,112 @@
{ {
"name": "coriolis_shipyard", "name": "coriolis_shipyard",
"version": "2.0.0", "version": "3.0.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/cmmcleod/coriolis" "url": "https://github.com/EDCD/coriolis"
}, },
"homepage": "https://coriolis.io", "homepage": "https://coriolis.io",
"bugs": "https://github.com/cmmcleod/coriolis/issues", "bugs": "https://github.com/EDCD/coriolis/issues",
"private": true, "private": true,
"engine": "node >= 4.0.0", "engine": "node >= 4.8.1",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f", "extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f",
"clean": "rimraf build", "clean": "rimraf build",
"start": "node devServer.js", "start": "node devServer.js",
"lint": "eslint --ext .js,.jsx src", "lint": "eslint --ext .js,.jsx src",
"test": "jest",
"prod-serve": "nginx -p $(pwd) -c nginx.conf", "prod-serve": "nginx -p $(pwd) -c nginx.conf",
"prod-stop": "kill -QUIT $(cat nginx.pid)", "prod-stop": "kill -QUIT $(cat nginx.pid)",
"build": "npm run clean && NODE_ENV=production webpack -d -p --config webpack.config.prod.js", "build": "npm run clean && cross-env NODE_ENV=production webpack -p --config webpack.config.prod.js",
"rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws", "rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws",
"deploy": "npm run lint && npm test && npm run build && npm run rsync" "deploy": "npm run lint && npm test && npm run build && npm run rsync"
}, },
"jest": {
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
"testFileExtensions": [
"js"
],
"moduleFileExtensions": [
"js",
"json",
"jsx"
],
"unmockedModulePathPatterns": [
"<rootDir>/node_modules/react",
"<rootDir>/node_modules/react-dom",
"<rootDir>/node_modules/react-addons-test-utils",
"<rootDir>/node_modules/react-testutils-additions",
"<rootDir>/node_modules/fbjs",
"<rootDir>/node_modules/fbemitter",
"<rootDir>/node_modules/classnames",
"<rootDir>/node_modules/d3",
"<rootDir>/node_modules/lz-string",
"<rootDir>/node_modules/jsen",
"coriolis-data",
"<rootDir>/src/app/shipyard",
"<rootDir>/src/app/i18n",
"<rootDir>/src/app/utils",
"<rootDir>/src/schemas",
"<rootDir>/__tests__"
]
},
"devDependencies": { "devDependencies": {
"appcache-webpack-plugin": "^1.2.1", "@babel/core": "^7.0.0",
"babel-core": "*", "@babel/plugin-proposal-class-properties": "^7.0.0",
"babel-eslint": "*", "@babel/plugin-proposal-decorators": "^7.0.0",
"babel-jest": "*", "@babel/plugin-proposal-do-expressions": "^7.0.0",
"babel-loader": "*", "@babel/plugin-proposal-export-default-from": "^7.0.0",
"babel-preset-es2015": "*", "@babel/plugin-proposal-export-namespace-from": "^7.0.0",
"babel-preset-react": "*", "@babel/plugin-proposal-function-bind": "^7.0.0",
"babel-preset-stage-0": "*", "@babel/plugin-proposal-function-sent": "^7.0.0",
"css-loader": "^0.23.0", "@babel/plugin-proposal-json-strings": "^7.0.0",
"eslint": "2.2.0", "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0",
"eslint-plugin-react": "^4.0.0", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
"expose-loader": "^0.7.1", "@babel/plugin-proposal-numeric-separator": "^7.0.0",
"express": "^4.13.3", "@babel/plugin-proposal-optional-chaining": "^7.0.0",
"extract-text-webpack-plugin": "^0.9.1", "@babel/plugin-proposal-pipeline-operator": "^7.0.0",
"file-loader": "^0.8.4", "@babel/plugin-proposal-throw-expressions": "^7.0.0",
"html-webpack-plugin": "^1.7.0", "@babel/plugin-syntax-dynamic-import": "^7.0.0",
"jest-cli": "^0.9.2", "@babel/plugin-syntax-import-meta": "^7.0.0",
"jsen": "^0.6.0", "@babel/preset-env": "^7.0.0",
"json-loader": "^0.5.3", "@babel/preset-react": "^7.0.0",
"less": "^2.5.3", "appcache-webpack-plugin": "^1.4.0",
"less-loader": "^2.2.1", "babel-core": "^7.0.0-bridge.0",
"react-addons-test-utils": "^15.0.1", "babel-eslint": "^10.0.1",
"react-testutils-additions": "^15.1.0", "babel-loader": "^8.0.0",
"rimraf": "^2.4.3", "copy-webpack-plugin": "^4.5.2",
"style-loader": "^0.13.0", "create-react-class": "^15.6.3",
"url-loader": "^0.5.6", "cross-env": "^5.2.0",
"webpack": "^1.9.6", "css-loader": "^1.0.0",
"webpack-dev-server": "^1.14.0" "d3-selection": "^1.3.2",
"esdoc": "^1.1.0",
"esdoc-custom-theme": "^1.4.2",
"esdoc-ecmascript-proposal-plugin": "^1.0.0",
"esdoc-jsx-plugin": "^1.0.0",
"esdoc-publish-html-plugin": "^1.1.2",
"esdoc-react-plugin": "^1.0.1",
"esdoc-standard-plugin": "^1.0.0",
"eslint": "^5.6.0",
"eslint-plugin-react": "^7.11.1",
"expose-loader": "^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",
"jsen": "^0.6.4",
"json-loader": "^0.5.4",
"less": "^3.8.1",
"less-loader": "^4.1.0",
"react-addons-perf": "^15.4.2",
"react-container-dimensions": "^1.4.1",
"react-testutils-additions": "^16.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"
}, },
"sideEffects": false,
"dependencies": { "dependencies": {
"babel-polyfill": "*", "@babel/polyfill": "^7.0.0",
"classnames": "^2.2.0", "auto-bind": "^2.1.1",
"coriolis-data": "cmmcleod/coriolis-data", "browserify-zlib-next": "^1.0.1",
"d3": "3.5.16", "classnames": "^2.2.6",
"fbemitter": "^2.0.0", "d3": "^5.7.0",
"detect-browser": "^3.0.1",
"ed-forge": "../ed-forge",
"fbemitter": "^2.1.1",
"lodash": "^4.17.11",
"lz-string": "^1.4.4", "lz-string": "^1.4.4",
"react": "^15.0.1", "pako": "^1.0.6",
"react-dom": "^15.0.1", "prop-types": "^15.6.2",
"superagent": "^1.4.0" "react": "^15.5.4",
"react-dom": "^15.5.4",
"react-extras": "^0.7.1",
"react-fuzzy": "^0.5.2",
"react-ga": "^2.5.3",
"react-number-editor": "^4.0.3",
"recharts": "^1.2.0",
"register-service-worker": "^1.5.2",
"superagent": "^3.8.3"
} }
} }

9
rollup.config.js Normal file
View File

@@ -0,0 +1,9 @@
import nodeResolve from "rollup-plugin-node-resolve";
export default {
entry: "d3-funcs.js",
format: "umd",
moduleName: "d3",
plugins: [nodeResolve({jsnext: true})],
dest: "d3.js"
};

906
src/.htaccess Normal file
View File

@@ -0,0 +1,906 @@
RewriteEngine on
# If a directory or a file exists, use the request directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# Otherwise forward the request to index.php
RewriteRule . index.html
# Apache Server Configs v2.10.0 | MIT License
# https://github.com/h5bp/server-configs-apache
# (!) Using `.htaccess` files slows down Apache, therefore, if you have
# access to the main server configuration file (which is usually called
# `httpd.conf`), you should add this logic there.
#
# https://httpd.apache.org/docs/current/howto/htaccess.html.
# ######################################################################
# # CROSS-ORIGIN #
# ######################################################################
# ----------------------------------------------------------------------
# | Cross-origin requests |
# ----------------------------------------------------------------------
# Allow cross-origin requests.
#
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
# http://enable-cors.org/
# http://www.w3.org/TR/cors/
# <IfModule mod_headers.c>
# Header set Access-Control-Allow-Origin "*"
# </IfModule>
# ----------------------------------------------------------------------
# | Cross-origin images |
# ----------------------------------------------------------------------
# Send the CORS header for images when browsers request it.
#
# https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
# https://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
<IfModule mod_setenvif.c>
<IfModule mod_headers.c>
<FilesMatch "\.(bmp|cur|gif|ico|jpe?g|png|svgz?|webp)$">
SetEnvIf Origin ":" IS_CORS
Header set Access-Control-Allow-Origin "*" env=IS_CORS
</FilesMatch>
</IfModule>
</IfModule>
# ----------------------------------------------------------------------
# | Cross-origin web fonts |
# ----------------------------------------------------------------------
# Allow cross-origin access to web fonts.
<IfModule mod_headers.c>
<FilesMatch "\.(eot|otf|tt[cf]|woff2?)$">
Header set Access-Control-Allow-Origin "*"
</FilesMatch>
</IfModule>
# ----------------------------------------------------------------------
# | Cross-origin resource timing |
# ----------------------------------------------------------------------
# Allow cross-origin access to the timing information for all resources.
#
# If a resource isn't served with a `Timing-Allow-Origin` header that
# would allow its timing information to be shared with the document,
# some of the attributes of the `PerformanceResourceTiming` object will
# be set to zero.
#
# http://www.w3.org/TR/resource-timing/
# http://www.stevesouders.com/blog/2014/08/21/resource-timing-practical-tips/
# <IfModule mod_headers.c>
# Header set Timing-Allow-Origin: "*"
# </IfModule>
# ######################################################################
# # ERRORS #
# ######################################################################
# ----------------------------------------------------------------------
# | Custom error messages/pages |
# ----------------------------------------------------------------------
# Customize what Apache returns to the client in case of an error.
# https://httpd.apache.org/docs/current/mod/core.html#errordocument
ErrorDocument 404 /404.html
# ----------------------------------------------------------------------
# | Error prevention |
# ----------------------------------------------------------------------
# Disable the pattern matching based on filenames.
#
# This setting prevents Apache from returning a 404 error as the result
# of a rewrite when the directory with the same name does not exist.
#
# https://httpd.apache.org/docs/current/content-negotiation.html#multiviews
Options -MultiViews
# ######################################################################
# # INTERNET EXPLORER #
# ######################################################################
# ----------------------------------------------------------------------
# | Document modes |
# ----------------------------------------------------------------------
# Force Internet Explorer 8/9/10 to render pages in the highest mode
# available in the various cases when it may not.
#
# https://hsivonen.fi/doctype/#ie8
#
# (!) Starting with Internet Explorer 11, document modes are deprecated.
# If your business still relies on older web apps and services that were
# designed for older versions of Internet Explorer, you might want to
# consider enabling `Enterprise Mode` throughout your company.
#
# http://msdn.microsoft.com/en-us/library/ie/bg182625.aspx#docmode
# http://blogs.msdn.com/b/ie/archive/2014/04/02/stay-up-to-date-with-enterprise-mode-for-internet-explorer-11.aspx
<IfModule mod_headers.c>
Header set X-UA-Compatible "IE=edge"
# `mod_headers` cannot match based on the content-type, however,
# the `X-UA-Compatible` response header should be send only for
# HTML documents and not for the other resources.
<FilesMatch "\.(appcache|atom|bbaw|bmp|crx|css|cur|eot|f4[abpv]|flv|geojson|gif|htc|ico|jpe?g|js|json(ld)?|m4[av]|manifest|map|mp4|oex|og[agv]|opus|otf|pdf|png|rdf|rss|safariextz|svgz?|swf|topojson|tt[cf]|txt|vcard|vcf|vtt|webapp|web[mp]|woff2?|xloc|xml|xpi)$">
Header unset X-UA-Compatible
</FilesMatch>
</IfModule>
# ----------------------------------------------------------------------
# | Iframes cookies |
# ----------------------------------------------------------------------
# Allow cookies to be set from iframes in Internet Explorer.
#
# http://msdn.microsoft.com/en-us/library/ms537343.aspx
# http://www.w3.org/TR/2000/CR-P3P-20001215/
# <IfModule mod_headers.c>
# Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
# </IfModule>
# ######################################################################
# # MEDIA TYPES AND CHARACTER ENCODINGS #
# ######################################################################
# ----------------------------------------------------------------------
# | Media types |
# ----------------------------------------------------------------------
# Serve resources with the proper media types (f.k.a. MIME types).
#
# https://www.iana.org/assignments/media-types/media-types.xhtml
# https://httpd.apache.org/docs/current/mod/mod_mime.html#addtype
<IfModule mod_mime.c>
# Data interchange
AddType application/json json map topojson
AddType application/ld+json jsonld
AddType application/vnd.geo+json geojson
AddType application/xml atom rdf rss xml
# JavaScript
# Normalize to standard type.
# https://tools.ietf.org/html/rfc4329#section-7.2
AddType application/javascript js
# Manifest files
# If you are providing a web application manifest file (see
# the specification: https://w3c.github.io/manifest/), it is
# recommended that you serve it with the `application/manifest+json`
# media type.
#
# Because the web application manifest file doesn't have its
# own unique file extension, you can set its media type either
# by matching:
#
# 1) the exact location of the file (this can be done using a
# directive such as `<Location>`, but it will NOT work in
# the `.htaccess` file, so you will have to do it in the main
# server configuration file or inside of a `<VirtualHost>`
# container)
#
# e.g.:
#
# <Location "/.well-known/manifest.json">
# AddType application/manifest+json json
# </Location>
#
# 2) the filename (this can be problematic as you will need to
# ensure that you don't have any other file with the same name
# as the one you gave to your web application manifest file)
#
# e.g.:
#
# <Files "manifest.json">
# AddType application/manifest+json json
# </Files>
AddType application/x-web-app-manifest+json webapp
AddType text/cache-manifest appcache manifest
# Media files
AddType audio/mp4 f4a f4b m4a
AddType audio/ogg oga ogg opus
AddType image/bmp bmp
AddType image/webp webp
AddType image/x-icon cur ico
AddType video/mp4 f4v f4p m4v mp4
AddType video/ogg ogv
AddType video/webm webm
AddType video/x-flv flv
AddType image/svg+xml svg svgz
# Web fonts
AddType application/font-woff woff
AddType application/font-woff2 woff2
AddType application/vnd.ms-fontobject eot
# Browsers usually ignore the font media types and simply sniff
# the bytes to figure out the font type.
# https://mimesniff.spec.whatwg.org/#matching-a-font-type-pattern
#
# However, Blink and WebKit based browsers will show a warning
# in the console if the following font types are served with any
# other media types.
AddType application/x-font-ttf ttc ttf
AddType font/opentype otf
# Other
AddType application/octet-stream safariextz
AddType application/x-bb-appworld bbaw
AddType application/x-chrome-extension crx
AddType application/x-opera-extension oex
AddType application/x-xpinstall xpi
AddType text/vcard vcard vcf
AddType text/vnd.rim.location.xloc xloc
AddType text/vtt vtt
AddType text/x-component htc
</IfModule>
# ----------------------------------------------------------------------
# | Character encodings |
# ----------------------------------------------------------------------
# Serve all resources labeled as `text/html` or `text/plain`
# with the media type `charset` parameter set to `UTF-8`.
#
# https://httpd.apache.org/docs/current/mod/core.html#adddefaultcharset
AddDefaultCharset utf-8
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Serve the following file types with the media type `charset`
# parameter set to `UTF-8`.
#
# https://httpd.apache.org/docs/current/mod/mod_mime.html#addcharset
<IfModule mod_mime.c>
AddCharset utf-8 .atom \
.bbaw \
.css \
.geojson \
.js \
.json \
.jsonld \
.rdf \
.rss \
.topojson \
.vtt \
.webapp \
.xloc \
.xml
</IfModule>
# ######################################################################
# # REWRITES #
# ######################################################################
# ----------------------------------------------------------------------
# | Rewrite engine |
# ----------------------------------------------------------------------
# (1) Turn on the rewrite engine (this is necessary in order for
# the `RewriteRule` directives to work).
#
# https://httpd.apache.org/docs/current/mod/mod_rewrite.html#RewriteEngine
#
# (2) Enable the `FollowSymLinks` option if it isn't already.
#
# https://httpd.apache.org/docs/current/mod/core.html#options
#
# (3) If your web host doesn't allow the `FollowSymlinks` option,
# you need to comment it out or remove it, and then uncomment
# the `Options +SymLinksIfOwnerMatch` line (4), but be aware
# of the performance impact.
#
# https://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks
#
# (4) Some cloud hosting services will require you set `RewriteBase`.
#
# http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-modrewrite-not-working-on-my-site
# https://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritebase
#
# (5) Depending on how your server is set up, you may also need to
# use the `RewriteOptions` directive to enable some options for
# the rewrite engine.
#
# https://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewriteoptions
<IfModule mod_rewrite.c>
# (1)
RewriteEngine On
# (2)
Options +FollowSymlinks
# (3)
# Options +SymLinksIfOwnerMatch
# (4)
# RewriteBase /
# (5)
# RewriteOptions <options>
</IfModule>
# ----------------------------------------------------------------------
# | Forcing `https://` |
# ----------------------------------------------------------------------
# Redirect from the `http://` to the `https://` version of the URL.
# https://wiki.apache.org/httpd/RewriteHTTPToHTTPS
# <IfModule mod_rewrite.c>
# RewriteEngine On
# RewriteCond %{HTTPS} !=on
# RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
# </IfModule>
# ----------------------------------------------------------------------
# | Suppressing / Forcing the `www.` at the beginning of URLs |
# ----------------------------------------------------------------------
# The same content should never be available under two different
# URLs, especially not with and without `www.` at the beginning.
# This can cause SEO problems (duplicate content), and therefore,
# you should choose one of the alternatives and redirect the other
# one.
#
# By default `Option 1` (no `www.`) is activated.
# http://no-www.org/faq.php?q=class_b
#
# If you would prefer to use `Option 2`, just comment out all the
# lines from `Option 1` and uncomment the ones from `Option 2`.
#
# (!) NEVER USE BOTH RULES AT THE SAME TIME!
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Option 1: rewrite www.example.com → example.com
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
</IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Option 2: rewrite example.com → www.example.com
#
# Be aware that the following might not be a good idea if you use "real"
# subdomains for certain parts of your website.
# <IfModule mod_rewrite.c>
# RewriteEngine On
# RewriteCond %{HTTPS} !=on
# RewriteCond %{HTTP_HOST} !^www\. [NC]
# RewriteCond %{SERVER_ADDR} !=127.0.0.1
# RewriteCond %{SERVER_ADDR} !=::1
# RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# </IfModule>
# ######################################################################
# # SECURITY #
# ######################################################################
# ----------------------------------------------------------------------
# | Clickjacking |
# ----------------------------------------------------------------------
# Protect website against clickjacking.
#
# The example below sends the `X-Frame-Options` response header with
# the value `DENY`, informing browsers not to display the content of
# the web page in any frame.
#
# This might not be the best setting for everyone. You should read
# about the other two possible values the `X-Frame-Options` header
# field can have: `SAMEORIGIN` and `ALLOW-FROM`.
# https://tools.ietf.org/html/rfc7034#section-2.1.
#
# Keep in mind that while you could send the `X-Frame-Options` header
# for all of your websites pages, this has the potential downside that
# it forbids even non-malicious framing of your content (e.g.: when
# users visit your website using a Google Image Search results page).
#
# Nonetheless, you should ensure that you send the `X-Frame-Options`
# header for all pages that allow a user to make a state changing
# operation (e.g: pages that contain one-click purchase links, checkout
# or bank-transfer confirmation pages, pages that make permanent
# configuration changes, etc.).
#
# Sending the `X-Frame-Options` header can also protect your website
# against more than just clickjacking attacks:
# https://cure53.de/xfo-clickjacking.pdf.
#
# https://tools.ietf.org/html/rfc7034
# http://blogs.msdn.com/b/ieinternals/archive/2010/03/30/combating-clickjacking-with-x-frame-options.aspx
# https://www.owasp.org/index.php/Clickjacking
# <IfModule mod_headers.c>
# Header set X-Frame-Options "DENY"
# # `mod_headers` cannot match based on the content-type, however,
# # the `X-Frame-Options` response header should be send only for
# # HTML documents and not for the other resources.
# <FilesMatch "\.(appcache|atom|bbaw|bmp|crx|css|cur|eot|f4[abpv]|flv|geojson|gif|htc|ico|jpe?g|js|json(ld)?|m4[av]|manifest|map|mp4|oex|og[agv]|opus|otf|pdf|png|rdf|rss|safariextz|svgz?|swf|topojson|tt[cf]|txt|vcard|vcf|vtt|webapp|web[mp]|woff2?|xloc|xml|xpi)$">
# Header unset X-Frame-Options
# </FilesMatch>
# </IfModule>
# ----------------------------------------------------------------------
# | Content Security Policy (CSP) |
# ----------------------------------------------------------------------
# Mitigate the risk of cross-site scripting and other content-injection
# attacks.
#
# This can be done by setting a `Content Security Policy` which
# whitelists trusted sources of content for your website.
#
# The example header below allows ONLY scripts that are loaded from the
# current website's origin (no inline scripts, no CDN, etc). That almost
# certainly won't work as-is for your website!
#
# For more details on how to craft a reasonable policy for your website,
# read: http://www.html5rocks.com/en/tutorials/security/content-security-policy/
# (or the specification: http://www.w3.org/TR/CSP11/). Also, to make
# things easier, you can use an online CSP header generator such as:
# http://cspisawesome.com/.
# <IfModule mod_headers.c>
# Header set Content-Security-Policy "script-src 'self'; object-src 'self'"
# # `mod_headers` cannot match based on the content-type, however,
# # the `Content-Security-Policy` response header should be send
# # only for HTML documents and not for the other resources.
# <FilesMatch "\.(appcache|atom|bbaw|bmp|crx|css|cur|eot|f4[abpv]|flv|geojson|gif|htc|ico|jpe?g|js|json(ld)?|m4[av]|manifest|map|mp4|oex|og[agv]|opus|otf|pdf|png|rdf|rss|safariextz|svgz?|swf|topojson|tt[cf]|txt|vcard|vcf|vtt|webapp|web[mp]|woff2?|xloc|xml|xpi)$">
# Header unset Content-Security-Policy
# </FilesMatch>
# </IfModule>
# ----------------------------------------------------------------------
# | File access |
# ----------------------------------------------------------------------
# Block access to directories without a default document.
#
# You should leave the following uncommented, as you shouldn't allow
# anyone to surf through every directory on your server (which may
# includes rather private places such as the CMS's directories).
<IfModule mod_autoindex.c>
Options -Indexes
</IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Block access to all hidden files and directories with the exception of
# the visible content from within the `/.well-known/` hidden directory.
#
# These types of files usually contain user preferences or the preserved
# state of an utility, and can include rather private places like, for
# example, the `.git` or `.svn` directories.
#
# The `/.well-known/` directory represents the standard (RFC 5785) path
# prefix for "well-known locations" (e.g.: `/.well-known/manifest.json`,
# `/.well-known/keybase.txt`), and therefore, access to its visible
# content should not be blocked.
#
# https://www.mnot.net/blog/2010/04/07/well-known
# https://tools.ietf.org/html/rfc5785
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_URI} "!(^|/)\.well-known/([^./]+./?)+$" [NC]
RewriteCond %{SCRIPT_FILENAME} -d [OR]
RewriteCond %{SCRIPT_FILENAME} -f
RewriteRule "(^|/)\." - [F]
</IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Block access to files that can expose sensitive information.
#
# By default, block access to backup and source files that may be
# left by some text editors and can pose a security risk when anyone
# has access to them.
#
# http://feross.org/cmsploit/
#
# (!) Update the `<FilesMatch>` regular expression from below to
# include any files that might end up on your production server and
# can expose sensitive information about your website. These files may
# include: configuration files, files that contain metadata about the
# project (e.g.: project dependencies), build scripts, etc..
<FilesMatch "(^#.*#|\.(bak|conf|dist|fla|in[ci]|log|psd|sh|sql|sw[op])|~)$">
# Apache < 2.3
<IfModule !mod_authz_core.c>
Order allow,deny
Deny from all
Satisfy All
</IfModule>
# Apache ≥ 2.3
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
</FilesMatch>
# ----------------------------------------------------------------------
# | HTTP Strict Transport Security (HSTS) |
# ----------------------------------------------------------------------
# Force client-side SSL redirection.
#
# If a user types `example.com` in their browser, even if the server
# redirects them to the secure version of the website, that still leaves
# a window of opportunity (the initial HTTP connection) for an attacker
# to downgrade or redirect the request.
#
# The following header ensures that browser will ONLY connect to your
# server via HTTPS, regardless of what the users type in the browser's
# address bar.
#
# (!) Remove the `includeSubDomains` optional directive if the website's
# subdomains are not using HTTPS.
#
# http://www.html5rocks.com/en/tutorials/security/transport-layer-security/
# https://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec-14#section-6.1
# http://blogs.msdn.com/b/ieinternals/archive/2014/08/18/hsts-strict-transport-security-attacks-mitigations-deployment-https.aspx
# <IfModule mod_headers.c>
# Header set Strict-Transport-Security "max-age=16070400; includeSubDomains"
# </IfModule>
# ----------------------------------------------------------------------
# | Reducing MIME type security risks |
# ----------------------------------------------------------------------
# Prevent some browsers from MIME-sniffing the response.
#
# This reduces exposure to drive-by download attacks and cross-origin
# data leaks, and should be left uncommented, especially if the server
# is serving user-uploaded content or content that could potentially be
# treated as executable by the browser.
#
# http://www.slideshare.net/hasegawayosuke/owasp-hasegawa
# http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx
# http://msdn.microsoft.com/en-us/library/ie/gg622941.aspx
# https://mimesniff.spec.whatwg.org/
<IfModule mod_headers.c>
Header set X-Content-Type-Options "nosniff"
</IfModule>
# ----------------------------------------------------------------------
# | Reflected Cross-Site Scripting (XSS) attacks |
# ----------------------------------------------------------------------
# (1) Try to re-enable the cross-site scripting (XSS) filter built
# into most web browsers.
#
# The filter is usually enabled by default, but in some cases it
# may be disabled by the user. However, in Internet Explorer for
# example, it can be re-enabled just by sending the
# `X-XSS-Protection` header with the value of `1`.
#
# (2) Prevent web browsers from rendering the web page if a potential
# reflected (a.k.a non-persistent) XSS attack is detected by the
# filter.
#
# By default, if the filter is enabled and browsers detect a
# reflected XSS attack, they will attempt to block the attack
# by making the smallest possible modifications to the returned
# web page.
#
# Unfortunately, in some browsers (e.g.: Internet Explorer),
# this default behavior may allow the XSS filter to be exploited,
# thereby, it's better to inform browsers to prevent the rendering
# of the page altogether, instead of attempting to modify it.
#
# http://hackademix.net/2009/11/21/ies-xss-filter-creates-xss-vulnerabilities
#
# (!) Do not rely on the XSS filter to prevent XSS attacks! Ensure that
# you are taking all possible measures to prevent XSS attacks, the
# most obvious being: validating and sanitizing your website's inputs.
#
# http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-iv-the-xss-filter.aspx
# http://blogs.msdn.com/b/ieinternals/archive/2011/01/31/controlling-the-internet-explorer-xss-filter-with-the-x-xss-protection-http-header.aspx
# https://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29
# <IfModule mod_headers.c>
# # (1) (2)
# Header set X-XSS-Protection "1; mode=block"
# # `mod_headers` cannot match based on the content-type, however,
# # the `X-XSS-Protection` response header should be send only for
# # HTML documents and not for the other resources.
# <FilesMatch "\.(appcache|atom|bbaw|bmp|crx|css|cur|eot|f4[abpv]|flv|geojson|gif|htc|ico|jpe?g|js|json(ld)?|m4[av]|manifest|map|mp4|oex|og[agv]|opus|otf|pdf|png|rdf|rss|safariextz|svgz?|swf|topojson|tt[cf]|txt|vcard|vcf|vtt|webapp|web[mp]|woff2?|xloc|xml|xpi)$">
# Header unset X-XSS-Protection
# </FilesMatch>
# </IfModule>
# ----------------------------------------------------------------------
# | Server software information |
# ----------------------------------------------------------------------
# Prevent Apache from sending in the `Server` response header its
# exact version number, the description of the generic OS-type or
# information about its compiled-in modules.
#
# (!) The `ServerTokens` directive will only work in the main server
# configuration file, so don't try to enable it in the `.htaccess` file!
#
# https://httpd.apache.org/docs/current/mod/core.html#servertokens
# ServerTokens Prod
# ######################################################################
# # WEB PERFORMANCE #
# ######################################################################
# ----------------------------------------------------------------------
# | Compression |
# ----------------------------------------------------------------------
<IfModule mod_deflate.c>
# Force compression for mangled `Accept-Encoding` request headers
# https://developer.yahoo.com/blogs/ydn/pushing-beyond-gzipping-25601.html
<IfModule mod_setenvif.c>
<IfModule mod_headers.c>
SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
</IfModule>
</IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Compress all output labeled with one of the following media types.
#
# (!) For Apache versions below version 2.3.7 you don't need to
# enable `mod_filter` and can remove the `<IfModule mod_filter.c>`
# and `</IfModule>` lines as `AddOutputFilterByType` is still in
# the core directives.
#
# https://httpd.apache.org/docs/current/mod/mod_filter.html#addoutputfilterbytype
<IfModule mod_filter.c>
AddOutputFilterByType DEFLATE "application/atom+xml" \
"application/javascript" \
"application/json" \
"application/ld+json" \
"application/manifest+json" \
"application/rdf+xml" \
"application/rss+xml" \
"application/schema+json" \
"application/vnd.geo+json" \
"application/vnd.ms-fontobject" \
"application/x-font-ttf" \
"application/x-javascript" \
"application/x-web-app-manifest+json" \
"application/xhtml+xml" \
"application/xml" \
"font/opentype" \
"image/bmp" \
"image/svg+xml" \
"image/x-icon" \
"text/cache-manifest" \
"text/css" \
"text/html" \
"text/javascript" \
"text/plain" \
"text/vcard" \
"text/vnd.rim.location.xloc" \
"text/vtt" \
"text/x-component" \
"text/xml"
</IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Map the following filename extensions to the specified
# encoding type in order to make Apache serve the file types
# with the appropriate `Content-Encoding` response header
# (do note that this will NOT make Apache compress them!).
#
# If these files types would be served without an appropriate
# `Content-Enable` response header, client applications (e.g.:
# browsers) wouldn't know that they first need to uncompress
# the response, and thus, wouldn't be able to understand the
# content.
#
# https://httpd.apache.org/docs/current/mod/mod_mime.html#addencoding
<IfModule mod_mime.c>
AddEncoding gzip svgz
</IfModule>
</IfModule>
# ----------------------------------------------------------------------
# | Content transformation |
# ----------------------------------------------------------------------
# Prevent intermediate caches or proxies (e.g.: such as the ones
# used by mobile network providers) from modifying the website's
# content.
#
# https://tools.ietf.org/html/rfc2616#section-14.9.5
#
# (!) If you are using `mod_pagespeed`, please note that setting
# the `Cache-Control: no-transform` response header will prevent
# `PageSpeed` from rewriting `HTML` files, and, if the
# `ModPagespeedDisableRewriteOnNoTransform` directive isn't set
# to `off`, also from rewriting other resources.
#
# https://developers.google.com/speed/pagespeed/module/configuration#notransform
# <IfModule mod_headers.c>
# Header merge Cache-Control "no-transform"
# </IfModule>
# ----------------------------------------------------------------------
# | ETags |
# ----------------------------------------------------------------------
# Remove `ETags` as resources are sent with far-future expires headers.
#
# https://developer.yahoo.com/performance/rules.html#etags
# https://tools.ietf.org/html/rfc7232#section-2.3
# `FileETag None` doesn't work in all cases.
<IfModule mod_headers.c>
Header unset ETag
</IfModule>
FileETag None
# ----------------------------------------------------------------------
# | Expires headers |
# ----------------------------------------------------------------------
# Serve resources with far-future expires headers.
#
# (!) If you don't control versioning with filename-based
# cache busting, you should consider lowering the cache times
# to something like one week.
#
# https://httpd.apache.org/docs/current/mod/mod_expires.html
<IfModule mod_expires.c>
ExpiresActive on
ExpiresDefault "access plus 1 month"
# CSS
ExpiresByType text/css "access plus 1 year"
# Data interchange
ExpiresByType application/atom+xml "access plus 1 hour"
ExpiresByType application/rdf+xml "access plus 1 hour"
ExpiresByType application/rss+xml "access plus 1 hour"
ExpiresByType application/json "access plus 0 seconds"
ExpiresByType application/ld+json "access plus 0 seconds"
ExpiresByType application/schema+json "access plus 0 seconds"
ExpiresByType application/vnd.geo+json "access plus 0 seconds"
ExpiresByType application/xml "access plus 0 seconds"
ExpiresByType text/xml "access plus 0 seconds"
# Favicon (cannot be renamed!) and cursor images
ExpiresByType image/x-icon "access plus 1 week"
# HTML
ExpiresByType text/html "access plus 0 seconds"
# JavaScript
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType application/x-javascript "access plus 1 year"
ExpiresByType text/javascript "access plus 1 year"
# Manifest files
ExpiresByType application/manifest+json "access plus 1 year"
ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds"
ExpiresByType text/cache-manifest "access plus 0 seconds"
# Media files
ExpiresByType audio/ogg "access plus 1 month"
ExpiresByType image/bmp "access plus 1 month"
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType video/mp4 "access plus 1 month"
ExpiresByType video/ogg "access plus 1 month"
ExpiresByType video/webm "access plus 1 month"
# Web fonts
ExpiresByType application/font-woff "access plus 1 month"
ExpiresByType application/font-woff2 "access plus 1 month"
ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
ExpiresByType application/x-font-ttf "access plus 1 month"
ExpiresByType font/opentype "access plus 1 month"
ExpiresByType image/svg+xml "access plus 1 month"
</IfModule>
# ----------------------------------------------------------------------
# | File concatenation |
# ----------------------------------------------------------------------
# Allow concatenation from within specific files.
#
# e.g.:
#
# If you have the following lines in a file called, for
# example, `main.combined.js`:
#
# <!--#include file="js/jquery.js" -->
# <!--#include file="js/jquery.timer.js" -->
#
# Apache will replace those lines with the content of the
# specified files.
# <IfModule mod_include.c>
# <FilesMatch "\.combined\.js$">
# Options +Includes
# AddOutputFilterByType INCLUDES application/javascript \
# application/x-javascript \
# text/javascript
# SetOutputFilter INCLUDES
# </FilesMatch>
# <FilesMatch "\.combined\.css$">
# Options +Includes
# AddOutputFilterByType INCLUDES text/css
# SetOutputFilter INCLUDES
# </FilesMatch>
# </IfModule>
# ----------------------------------------------------------------------
# | Filename-based cache busting |
# ----------------------------------------------------------------------
# If you're not using a build process to manage your filename version
# revving, you might want to consider enabling the following directives
# to route all requests such as `/style.12345.css` to `/style.css`.
#
# To understand why this is important and even a better solution than
# using something like `*.css?v231`, please see:
# http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/
# <IfModule mod_rewrite.c>
# RewriteEngine On
# RewriteCond %{REQUEST_FILENAME} !-f
# RewriteRule ^(.+)\.(\d+)\.(bmp|css|cur|gif|ico|jpe?g|js|png|svgz?|webp)$ $1.$3 [L]
# </IfModule>

View File

@@ -1,17 +1,21 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import Router from './Router'; import Router from './Router';
import { register } from 'register-service-worker';
import { EventEmitter } from 'fbemitter'; import { EventEmitter } from 'fbemitter';
import { getLanguage } from './i18n/Language'; import { getLanguage } from './i18n/Language';
import Persist from './stores/Persist'; import Persist from './stores/Persist';
import { Ship } from 'ed-forge';
import Announcement from './components/Announcement';
import Header from './components/Header'; import Header from './components/Header';
import Tooltip from './components/Tooltip'; import Tooltip from './components/Tooltip';
import ModalHelp from './components/ModalHelp';
import ModalImport from './components/ModalImport'; import ModalImport from './components/ModalImport';
import ModalPermalink from './components/ModalPermalink';
import AboutPage from './pages/AboutPage'; import AboutPage from './pages/AboutPage';
import NotFoundPage from './pages/NotFoundPage'; import NotFoundPage from './pages/NotFoundPage';
import OutfittingPage from './pages/OutfittingPage'; import OutfittingPage from './pages/OutfittingPage';
import ComparisonPage from './pages/ComparisonPage';
import ShipyardPage from './pages/ShipyardPage'; import ShipyardPage from './pages/ShipyardPage';
import ErrorDetails from './pages/ErrorDetails'; import ErrorDetails from './pages/ErrorDetails';
@@ -19,20 +23,19 @@ import ErrorDetails from './pages/ErrorDetails';
* Coriolis App * Coriolis App
*/ */
export default class Coriolis extends React.Component { export default class Coriolis extends React.Component {
static childContextTypes = { static childContextTypes = {
closeMenu: React.PropTypes.func.isRequired, closeMenu: PropTypes.func.isRequired,
hideModal: React.PropTypes.func.isRequired, hideModal: PropTypes.func.isRequired,
language: React.PropTypes.object.isRequired, language: PropTypes.object.isRequired,
noTouch: React.PropTypes.bool.isRequired, noTouch: PropTypes.bool.isRequired,
onCommand: React.PropTypes.func.isRequired, onCommand: PropTypes.func.isRequired,
onWindowResize: React.PropTypes.func.isRequired, onWindowResize: PropTypes.func.isRequired,
openMenu: React.PropTypes.func.isRequired, openMenu: PropTypes.func.isRequired,
route: React.PropTypes.object.isRequired, route: PropTypes.object.isRequired,
showModal: React.PropTypes.func.isRequired, showModal: PropTypes.func.isRequired,
sizeRatio: React.PropTypes.number.isRequired, sizeRatio: PropTypes.number.isRequired,
termtip: React.PropTypes.func.isRequired, termtip: PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired tooltip: PropTypes.func.isRequired
}; };
/** /**
@@ -57,19 +60,49 @@ export default class Coriolis extends React.Component {
this.state = { this.state = {
noTouch: !('ontouchstart' in window || navigator.msMaxTouchPoints || navigator.maxTouchPoints), noTouch: !('ontouchstart' in window || navigator.msMaxTouchPoints || navigator.maxTouchPoints),
page: null, page: null,
announcements: [],
language: getLanguage(Persist.getLangCode()), language: getLanguage(Persist.getLangCode()),
route: {}, route: {},
sizeRatio: Persist.getSizeRatio() sizeRatio: Persist.getSizeRatio()
}; };
// TODO: New mechanism for announcements
// this._getAnnouncements();
Router('', (r) => this._setPage(ShipyardPage, r)); Router('', (r) => this._setPage(ShipyardPage, r));
Router('/import?', (r) => this._importBuild(r));
Router('/import/:data', (r) => this._importBuild(r));
Router('/outfit/?', (r) => this._setPage(OutfittingPage, r));
Router('/outfit/:ship/?', (r) => this._setPage(OutfittingPage, r));
Router('/outfit/:ship/:code?', (r) => this._setPage(OutfittingPage, r)); Router('/outfit/:ship/:code?', (r) => this._setPage(OutfittingPage, r));
Router('/compare/:name?', (r) => this._setPage(ComparisonPage, r));
Router('/comparison/:code', (r) => this._setPage(ComparisonPage, r));
Router('/about', (r) => this._setPage(AboutPage, r)); Router('/about', (r) => this._setPage(AboutPage, r));
Router('*', (r) => this._setPage(null, r)); Router('*', (r) => this._setPage(null, r));
} }
/**
* Import a build directly
* @param {Object} r The current route
*/
_importBuild(r) {
try {
// Need to decode and gunzip the data, then build the ship
let ship = new Ship(r.params.data);
r.params.ship = ship.getShipType();
r.params.code = ship.compress();
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 * Updates / Sets the page and route context
* @param {[type]} page The page to be shown * @param {[type]} page The page to be shown
@@ -93,6 +126,13 @@ export default class Coriolis extends React.Component {
*/ */
_onError(msg, scriptUrl, line, col, errObj) { _onError(msg, scriptUrl, line, col, errObj) {
console && console.error && console.error(arguments); // eslint-disable-line no-console console && console.error && console.error(arguments); // eslint-disable-line no-console
if (errObj) {
if (errObj instanceof Error) {
bugsnagClient.notify(errObj); // eslint-disable-line
} else if (errObj instanceof String) {
bugsnagClient.notify(msg, errObj); // eslint-disable-line
}
}
this.setState({ this.setState({
error: <ErrorDetails error={{ message: msg, details: { scriptUrl, line, col, error: JSON.stringify(errObj) } }}/>, error: <ErrorDetails error={{ message: msg, details: { scriptUrl, line, col, error: JSON.stringify(errObj) } }}/>,
page: null, page: null,
@@ -131,14 +171,26 @@ export default class Coriolis extends React.Component {
this._hideModal(); this._hideModal();
this._closeMenu(); this._closeMenu();
break; break;
case 72: // 'h'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + h
e.preventDefault();
this._showModal(<ModalHelp/>);
}
break;
case 73: // 'i' case 73: // 'i'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i
e.preventDefault(); e.preventDefault();
this._showModal(<ModalImport />); this._showModal(<ModalImport/>);
} }
break; break;
case 101010: // 's' case 79: // 'o'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i if (e.ctrlKey || e.metaKey) { // CTRL/CMD + o
e.preventDefault();
this._showModal(<ModalPermalink url={window.location.href}/>);
}
break;
case 83: // 's'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + s
e.preventDefault(); e.preventDefault();
this.emitter.emit('command', 'save'); this.emitter.emit('command', 'save');
} }
@@ -150,7 +202,7 @@ export default class Coriolis extends React.Component {
* @param {React.Component} content Modal Content * @param {React.Component} content Modal Content
*/ */
_showModal(content) { _showModal(content) {
let modal = <div className='modal-bg' onClick={(e) => this._hideModal() }>{content}</div>; let modal = <div className='modal-bg' onClick={(e) => this._hideModal()}>{content}</div>;
this.setState({ modal }); this.setState({ modal });
} }
@@ -199,14 +251,19 @@ export default class Coriolis extends React.Component {
/** /**
* Show the term tip * Show the term tip
* @param {string} term Term or Phrase * @param {string} term Term or Phrase
* @param {Object} opts Options - dontCap, orientation (n,e,s,w) * @param {Object} opts Options - dontCap, orientation (n,e,s,w) (can also be the event if no options supplied)
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
* @param {SyntheticEvent} e2 Alternative location for synthetic event from charts (where 'Event' is actually a chart index)
*/ */
_termtip(term, opts, event) { _termtip(term, opts, event, e2) {
if (opts && opts.nativeEvent) { // Opts is a SyntheticEvent if (opts && opts.nativeEvent) { // Opts is the SyntheticEvent
event = opts; event = opts;
opts = { cap: true }; opts = { cap: true };
} }
if (e2 instanceof Object && e2.nativeEvent) { // E2 is the SyntheticEvent
event = e2;
}
this._tooltip( this._tooltip(
<div className={'cen' + (opts.cap ? ' cap' : '')}>{this.state.language.translate(term)}</div>, <div className={'cen' + (opts.cap ? ' cap' : '')}>{this.state.language.translate(term)}</div>,
event.currentTarget.getBoundingClientRect(), event.currentTarget.getBoundingClientRect(),
@@ -223,7 +280,7 @@ export default class Coriolis extends React.Component {
return this.emitter.addListener('windowResize', listener); return this.emitter.addListener('windowResize', listener);
} }
/** /**
* Add a listener to global commands such as save, * Add a listener to global commands such as save,
* @param {Function} listener Listener callback * @param {Function} listener Listener callback
* @return {Object} Subscription token * @return {Object} Subscription token
@@ -259,14 +316,50 @@ export default class Coriolis extends React.Component {
*/ */
componentWillMount() { componentWillMount() {
// Listen for appcache updated event, present refresh to update view // Listen for appcache updated event, present refresh to update view
if (window.applicationCache) { // Check that service workers are registered
window.applicationCache.addEventListener('updateready', () => { if (navigator.storage && navigator.storage.persist) {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) { window.addEventListener('load', () => {
this.setState({ appCacheUpdate: true }); // Browser downloaded a new app cache. navigator.storage.persist().then(granted => {
} if (granted)
console.log('Storage will not be cleared except by explicit user action');
else
console.log('Storage may be cleared by the UA under storage pressure.');
});
}); });
} }
if ('serviceWorker' in navigator) {
// Your service-worker.js *must* be located at the top-level directory relative to your site.
// It won't be able to control pages unless it's located at the same level or higher than them.
// *Don't* register service worker file in, e.g., a scripts/ sub-directory!
// See https://github.com/slightlyoff/ServiceWorker/issues/468
const self = this;
if (process.env.NODE_ENV === 'production') {
register('/service-worker.js', {
ready(registration) {
console.log('Service worker is active.');
},
registered(registration) {
console.log('Service worker has been registered.');
},
cached(registration) {
console.log('Content has been cached for offline use.');
},
updatefound(registration) {
console.log('New content is downloading.');
},
updated(registration) {
self.setState({ appCacheUpdate: true });
console.log('New content is available; please refresh.');
},
offline() {
console.log('No internet connection found. App is running in offline mode.');
},
error(error) {
console.error('Error during service worker registration:', error);
}
});
}
}
window.onerror = this._onError.bind(this); window.onerror = this._onError.bind(this);
window.addEventListener('resize', () => this.emitter.emit('windowResize')); window.addEventListener('resize', () => this.emitter.emit('windowResize'));
document.getElementById('coriolis').addEventListener('scroll', () => this._tooltip()); document.getElementById('coriolis').addEventListener('scroll', () => this._tooltip());
@@ -283,15 +376,28 @@ export default class Coriolis extends React.Component {
*/ */
render() { render() {
let currentMenu = this.state.currentMenu; let currentMenu = this.state.currentMenu;
return <div style={{ minHeight: '100%' }} onClick={this._closeMenu}
return <div style={{ minHeight: '100%' }} onClick={this._closeMenu} className={ this.state.noTouch ? 'no-touch' : null }> className={this.state.noTouch ? 'no-touch' : null}
<Header appCacheUpdate={this.state.appCacheUpdate} currentMenu={currentMenu} /> >
{ this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) : <NotFoundPage/> } <Header announcements={this.state.announcements} appCacheUpdate={this.state.appCacheUpdate}
{ this.state.modal } currentMenu={currentMenu}/>
{ this.state.tooltip } {/* <div className="announcement-container">{this.state.announcements.map(a => <Announcement
text={a.message}/>)}</div> */}
{this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) :
<NotFoundPage/>}
{this.state.modal}
{this.state.tooltip}
<footer> <footer>
<div className="right cap"> <div className="right cap">
<a href="https://github.com/cmmcleod/coriolis/releases/" target="_blank" title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a> <a href="https://github.com/EDCD/coriolis" target="_blank" rel="noopener noreferrer"
title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
<br/>
<a
href={'https://github.com/EDCD/coriolis/compare/edcd:develop@{' + window.CORIOLIS_DATE + '}...edcd:develop'}
target="_blank" rel="noopener noreferrer" title={'Coriolis Commits since' + window.CORIOLIS_DATE}>Commits
since last release
({window.CORIOLIS_DATE})</a>
</div> </div>
</footer> </footer>
</div>; </div>;

View File

@@ -72,11 +72,21 @@ Router.go = function(path, state) {
gaTrack(path); gaTrack(path);
let ctx = new Context(path, state); let ctx = new Context(path, state);
Router.dispatch(ctx); Router.dispatch(ctx);
if (!ctx.unhandled) { if (!ctx.unhandled) {
if (isStandAlone()) { if (isStandAlone()) {
Persist.setState(ctx); Persist.setState(ctx);
} }
history.pushState(ctx.state, ctx.title, ctx.canonicalPath); try {
history.pushState(ctx.state, ctx.title, ctx.canonicalPath);
} catch (ex) {
sessionStorage.setItem('__safari_history_fix', JSON.stringify({
state: ctx.state,
title: ctx.title,
path: ctx.canonicalPath
}));
location.reload();
}
} }
return ctx; return ctx;
}; };
@@ -99,7 +109,16 @@ Router.replace = function(path, state, dispatch) {
if (isStandAlone()) { if (isStandAlone()) {
Persist.setState(ctx); Persist.setState(ctx);
} }
history.replaceState(ctx.state, ctx.title, ctx.canonicalPath); try {
history.replaceState(ctx.state, ctx.title, ctx.canonicalPath);
} catch (ex) {
sessionStorage.setItem('__safari_history_fix', JSON.stringify({
state: ctx.state,
title: ctx.title,
path: ctx.canonicalPath
}));
location.reload();
}
return ctx; return ctx;
}; };
@@ -239,9 +258,8 @@ Route.prototype.match = function(path, params) {
* @param {string} path Path to track * @param {string} path Path to track
*/ */
function gaTrack(path) { function gaTrack(path) {
if (window.ga) { const _paq = window._paq || [];
window.ga('send', 'pageview', path); _paq.push(['trackPageView']);
}
} }
/** /**

View File

@@ -16,7 +16,6 @@ function isActive(href) {
* Active Link - Highlighted when URL matches window location * Active Link - Highlighted when URL matches window location
*/ */
export default class ActiveLink extends Link { export default class ActiveLink extends Link {
/** /**
* Renders the component * Renders the component
* @return {React.Component} The active link * @return {React.Component} The active link
@@ -29,5 +28,4 @@ export default class ActiveLink extends Link {
return <a {...this.props} className={className} onClick={this.handler}>{this.props.children}</a>; return <a {...this.props} className={className} onClick={this.handler}>{this.props.children}</a>;
} }
}
}

View File

@@ -0,0 +1,29 @@
import React from 'react';
import PropTypes from 'prop-types';
import { autoBind } from 'react-extras';
/**
* Announcement component
*/
export default class Announcement extends React.Component {
static propTypes = {
text: PropTypes.string
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
autoBind(this);
}
/**
* Renders the announcement
* @return {React.Component} A href element
*/
render() {
return <div className="announcement" >{this.props.text}</div>;
}
}

View File

@@ -1,28 +1,34 @@
import React from 'react'; import React from 'react';
import { findDOMNode } from 'react-dom'; import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import { stopCtxPropagation } from '../utils/UtilityFunctions'; import { stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames'; import cn from 'classnames';
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons'; import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import FuzzySearch from 'react-fuzzy';
import { getModuleInfo } from 'ed-forge/lib/src/data/items';
import { get, groupBy, mapValues, sortBy, zip, zipWith } from 'lodash';
import autoBind from 'auto-bind';
import MODULE_STATS from 'ed-forge/lib/src/module-stats';
import { SHOW } from '../shipyard/StatsMapping';
const PRESS_THRESHOLD = 500; // mouse/touch down threshold const PRESS_THRESHOLD = 500; // mouse/touch down threshold
const MOUNT_MAP = {
fixed: <MountFixed className={'lg'} />,
gimbal: <MountGimballed className={'lg'} />,
turret: <MountTurret className={'lg'} />,
};
/** /**
* Available modules menu * Available modules menu
*/ */
export default class AvailableModulesMenu extends TranslatedComponent { export default class AvailableModulesMenu extends TranslatedComponent {
static propTypes = { static propTypes = {
modules: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.array]).isRequired, onSelect: PropTypes.func.isRequired,
onSelect: React.PropTypes.func.isRequired, hideSearch: PropTypes.bool,
diffDetails: React.PropTypes.func, m: PropTypes.object,
m: React.PropTypes.object, warning: PropTypes.func,
shipMass: React.PropTypes.number, slotDiv: PropTypes.object
warning: React.PropTypes.func
};
static defaultProps = {
shipMass: 0
}; };
/** /**
@@ -32,7 +38,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
*/ */
constructor(props, context) { constructor(props, context) {
super(props); super(props);
this._hideDiff = this._hideDiff.bind(this); autoBind(this);
this.state = this._initState(props, context); this.state = this._initState(props, context);
} }
@@ -43,118 +49,230 @@ export default class AvailableModulesMenu extends TranslatedComponent {
* @return {Object} list: Array of React Components, currentGroup Component if any * @return {Object} list: Array of React Components, currentGroup Component if any
*/ */
_initState(props, context) { _initState(props, context) {
let translate = context.language.translate; const { translate } = context.language;
let { m, warning, shipMass, onSelect, modules } = props; const { m } = props;
let list, currentGroup; const list = [], fuzzy = [];
let buildGroup = this._buildGroup.bind( let currentGroup;
this,
translate, const modules = m.getApplicableItems().map(getModuleInfo);
m, const groups = mapValues(
warning, groupBy(modules, (info) => info.meta.group),
shipMass - (m && m.mass ? m.mass : 0), (infos) => groupBy(infos, (info) => info.meta.type),
(m, event) => {
this._hideDiff(event);
onSelect(m);
}
); );
// Build categories sorted by translated category name
const groupKeys = sortBy(Object.keys(groups), translate);
for (const group of groupKeys) {
const groupName = translate(group);
if (groupKeys.length > 1) {
list.push(<div key={`group-${group}`} className="select-category upp">{groupName}</div>);
}
if (modules instanceof Array) { const categories = groups[group];
list = buildGroup(modules[0].grp, modules); const categoryKeys = sortBy(Object.keys(categories), translate);
} else { for (const category of categoryKeys) {
list = []; const categoryName = translate(category);
// At present time slots with grouped options (Hardpoints and Internal) can be empty const infos = categories[category];
list.push(<div className='empty-c upp' key='empty' onClick={onSelect.bind(null, null)} >{translate('empty')}</div>); if (categoryKeys.length > 1) {
for (let g in modules) { list.push(<div key={`category-${category}`} className="select-group cap">{categoryName}</div>);
if (m && g == m.grp) {
list.push(<div ref={(elem) => this.groupElem = elem} key={g} className={'select-group cap'}>{translate(g)}</div>);
} else {
list.push(<div key={g} className={'select-group cap'}>{translate(g)}</div>);
} }
list.push(
list.push(buildGroup(g, modules[g])); this._buildGroup(
m,
category,
infos,
),
);
fuzzy.push(
...infos.map((info) => {
const { meta } = info;
const mount = meta.mount ? ' ' + translate(meta.mount) : '';
return {
grp: groupName,
cat: categoryName,
m: info.proto.Item,
name: `${meta.class}${meta.rating}${mount} ${categoryName}`,
};
}),
);
} }
} }
return { list, currentGroup, fuzzy, trackingFocus: false };
return { list, currentGroup };
} }
/** /**
* Generate React Components for Module Group * Generate React Components for Module Group
* @param {Function} translate Translate function * @param {Object} mountedModule Mounted Module
* @param {Objecy} mountedModule Mounted Module * @param {String} category Category key
* @param {Funciton} warningFunc Warning function
* @param {number} mass Mass
* @param {function} onSelect Select/Mount callback
* @param {string} grp Group name
* @param {Array} modules Available modules * @param {Array} modules Available modules
* @return {React.Component} Available Module Group contents * @return {React.Component} Available Module Group contents
*/ */
_buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules) { _buildGroup(mountedModule, category, modules) {
let prevClass = null, prevRating = null; const { warning } = this.props;
let elems = []; const ship = mountedModule.getShip();
const classMapping = groupBy(modules, (info) => info.meta.class);
for (let i = 0; i < modules.length; i++) { const itemsPerClass = Math.max(
let m = modules[i]; ...Object.values(classMapping).map((l) => l.length),
let mount = null; );
let disabled = m.maxmass && (mass + (m.mass ? m.mass : 0)) > m.maxmass; const itemsPerRow = itemsPerClass <= 2 ? 6 : itemsPerClass;
let active = mountedModule && mountedModule === m; // Nested array of <li> elements; will be flattened before being rendered.
let classes = cn(m.name ? 'lc' : 'c', { // Each sub-array represents one row in the final view.
warning: !disabled && warningFunc && warningFunc(m), const elems = [[]];
active,
disabled
});
let eventHandlers;
if (disabled || active) { // Reverse sort for descending order of module class
eventHandlers = {}; for (const clazz of Object.keys(classMapping).sort().reverse()) {
} else { for (let info of sortBy(
let showDiff = this._showDiff.bind(this, mountedModule, m); classMapping[clazz],
let select = onSelect.bind(null, m); (info) => info.meta.mount || info.meta.rating,
)) {
const { meta } = info;
const { Item } = info.proto;
eventHandlers = { // Can only be true if shieldgenmaximalmass is defined, i.e. this
onMouseEnter: this._over.bind(this, showDiff), // module must be a shield generator
onTouchStart: this._touchStart.bind(this, showDiff), let disabled = info.props.shieldgenmaximalmass < ship.readProp('hullmass');
onTouchEnd: this._touchEnd.bind(this, select), if (meta.experimental && !mountedModule.readMeta('experimental')) {
onMouseLeave: this._hideDiff, disabled =
onClick: select 4 <=
}; ship.getHardpoints().filter((m) => m.readMeta('experimental'))
.length;
}
// Default event handlers for objects that are disabled
let eventHandlers = {};
if (!disabled) {
const showDiff = this._showDiff.bind(this, mountedModule, info);
const select = (event) => {
this._hideDiff(event);
this.props.onSelect(Item);
};
eventHandlers = {
onMouseEnter: this._over.bind(this, showDiff),
onTouchStart: this._touchStart.bind(this, showDiff),
onTouchEnd: this._touchEnd.bind(this, select),
onMouseLeave: this._hideDiff,
onClick: select,
};
}
const mountSymbol = MOUNT_MAP[meta.mount];
const li = (
<li key={Item} data-id={Item}
ref={Item === mountedModule.getItem() ? (ref) => { this.activeSlotRef = ref; } : undefined}
className={cn(meta.type === 'armour' ? 'lc' : 'c', {
warning: !disabled && warning && warning(info),
active: mountedModule.getItem() === Item,
disabled,
hardpoint: mountSymbol,
})}
{...eventHandlers}
>{mountSymbol}{meta.type === 'armour' ? Item : `${meta.class}${meta.rating}`}</li>
);
const tail = elems.pop();
let newTail = [tail];
if (tail.length < itemsPerRow) {
// If the row has not grown too long, the new <li> element can be
// added to the row itself
tail.push(li);
} else {
// Otherwise, the last row gets a line break element added and this
// item is put into a new row
tail.push(<br key={elems.length}/>);
newTail.push([li]);
}
elems.push(...newTail);
} }
switch(m.mount) {
case 'F': mount = <MountFixed className={'lg'} />; break;
case 'G': mount = <MountGimballed className={'lg'}/>; break;
case 'T': mount = <MountTurret className={'lg'}/>; break;
}
if (i > 0 && modules.length > 3 && m.class != prevClass && (m.rating != prevRating || m.mount) && m.grp != 'pa') {
elems.push(<br key={'b' + m.grp + i} />);
}
elems.push(
<li key={m.id} className={classes} {...eventHandlers}>
{mount}
{(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')}
</li>
);
prevClass = m.class;
prevRating = m.rating;
} }
return <ul key={'modules' + grp} >{elems}</ul>; return <ul key={'ul' + category}>{[].concat(...elems)}</ul>;
} }
/** /**
* Generate tooltip content for the difference between the * Generate tooltip content for the difference between the
* mounted module and the hovered modules * mounted module and the hovered modules
* @param {Object} mm The module mounet currently * @param {Object} mountedModule The module mounted currently
* @param {Object} m The hovered module * @param {Object} hoveringModule The hovered module
* @param {DOMRect} rect DOMRect for target element * @param {DOMRect} rect DOMRect for target element
*/ */
_showDiff(mm, m, rect) { _showDiff(mountedModule, hoveringModule, rect) {
if (this.props.diffDetails) { const { tooltip, language } = this.context;
this.touchTimeout = null; const { formats, translate, units } = language;
this.context.tooltip(this.props.diffDetails(m, mm), rect); this.touchTimeout = null;
const mountedIsEmpty = mountedModule.isEmpty();
const props = (
mountedIsEmpty ? ['mass'] : Object.keys(hoveringModule.props)
).map((prop) => SHOW[prop] ? SHOW[prop].as : prop);
const oldProps = mountedIsEmpty ?
[{ value: 0 }] :
props.map((prop) => mountedModule.getFormatted(prop, false));
const newProps = mountedModule.try(() => {
mountedModule.setItem(hoveringModule.proto.Item);
return props.map((prop) => mountedModule.getFormatted(prop, false));
});
const diffs = zipWith(oldProps, newProps, (oldVal, newVal) => {
const { unit, value } = newVal;
if (!oldVal.value) {
return undefined;
}
return { value, diff: value - oldVal.value, unit };
});
const namedDiffs = zip(props, diffs).filter(([_, stat]) => stat !== undefined);
namedDiffs.push(['cost', {
value: hoveringModule.meta.cost,
diff: hoveringModule.meta.cost - (mountedIsEmpty ? 0 : mountedModule.readMeta('cost')),
unit: units.CR,
}]);
const tt = <div className='cap' style={{ whiteSpace: 'nowrap' }}>
{sortBy(namedDiffs, ([prop, _]) => prop).map(([prop, stats]) => {
const { unit, value, diff } = stats;
const beneficial = get(MODULE_STATS, [prop, 'higherbetter'], false) === diff > 0;
return <div key={prop}>
{translate(prop)}: <span className={diff === 0 ? 'disabled' : beneficial ? 'secondary' : 'warning'}>
{formats.round(value)} {diff !== 0 && ` (${diff > 0 ? '+' : ''}${formats.round(diff)})`}{unit}
</span>
</div>;
})}
</div>;
tooltip(tt, rect);
}
/**
* Generate tooltip content for the difference between the
* mounted module and the hovered modules
* @returns {React.Component} Search component if available
*/
_showSearch() {
if (this.props.hideSearch) {
return;
} }
return (
<FuzzySearch
list={this.state.fuzzy}
keys={['grp', 'name']}
tokenize={true}
className={'input'}
width={'100%'}
style={{ padding: 0 }}
onSelect={e => this.props.onSelect.bind(null, e.m)()}
resultsTemplate={(props, state, styles, clickHandler) => {
return state.results.map((val, i) => {
return (
<div
key={i}
className={'lc'}
onClick={() => clickHandler(i)}
>
{val.name}
</div>
);
});
}}
/>
);
} }
/** /**
@@ -205,8 +323,18 @@ export default class AvailableModulesMenu extends TranslatedComponent {
* Scroll to mounted (if it exists) module group on mount * Scroll to mounted (if it exists) module group on mount
*/ */
componentDidMount() { componentDidMount() {
if (this.groupElem) { // Scroll to currently selected group if (this.activeSlotRef) {
findDOMNode(this).scrollTop = this.groupElem.offsetTop; this.activeSlotRef.focus();
}
}
/**
* Handle focus if the component updates
*
*/
componentWillUnmount() {
if (this.props.slotDiv) {
this.props.slotDiv.focus();
} }
} }
@@ -225,15 +353,15 @@ export default class AvailableModulesMenu extends TranslatedComponent {
*/ */
render() { render() {
return ( return (
<div <div ref={node => this.node = node}
className={cn('select', this.props.className)} className={cn('select', this.props.className)}
onScroll={this._hideDiff} onScroll={this._hideDiff}
onClick={(e) => e.stopPropagation() } onClick={(e) => e.stopPropagation()}
onContextMenu={stopCtxPropagation} onContextMenu={stopCtxPropagation}
> >
{this._showSearch()}
{this.state.list} {this.state.list}
</div> </div>
); );
} }
} }

View File

@@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import d3 from 'd3'; import PropTypes from 'prop-types';
import * as d3 from 'd3';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
const MARGIN = { top: 15, right: 20, bottom: 40, left: 150 }; const MARGIN = { top: 15, right: 20, bottom: 40, left: 150 };
@@ -37,24 +38,23 @@ function insertLinebreaks(d) {
* Bar Chart * Bar Chart
*/ */
export default class BarChart extends TranslatedComponent { export default class BarChart extends TranslatedComponent {
static defaultProps = { static defaultProps = {
colors: ['#7b6888', '#6b486b', '#3182bd', '#a05d56', '#d0743c'], colors: ['#7b6888', '#6b486b', '#3182bd', '#a05d56', '#d0743c'],
labels: null, labels: null,
unit: '' unit: ''
}; };
static PropTypes = { static propTypes = {
colors: React.PropTypes.array, colors: PropTypes.array,
data: React.PropTypes.array.isRequired, data: PropTypes.array.isRequired,
desc: React.PropTypes.bool, desc: PropTypes.bool,
format: React.PropTypes.string.isRequired, format: PropTypes.string.isRequired,
labels: React.PropTypes.array, labels: PropTypes.array,
predicate: React.PropTypes.string, predicate: PropTypes.string,
properties: React.PropTypes.array, properties: PropTypes.array,
title: React.PropTypes.string.isRequired, title: PropTypes.string.isRequired,
unit: React.PropTypes.string.isRequired, unit: PropTypes.string.isRequired,
width: React.PropTypes.number.isRequired width: PropTypes.number.isRequired
}; };
/** /**
@@ -68,13 +68,13 @@ export default class BarChart extends TranslatedComponent {
this._updateDimensions = this._updateDimensions.bind(this); this._updateDimensions = this._updateDimensions.bind(this);
this._hideTip = this._hideTip.bind(this); this._hideTip = this._hideTip.bind(this);
let scale = d3.scale.linear(); let scale = d3.scaleLinear();
let y0 = d3.scale.ordinal(); let y0 = d3.scaleBand();
let y1 = d3.scale.ordinal(); let y1 = d3.scaleBand();
this.xAxis = d3.svg.axis().scale(scale).ticks(5).outerTickSize(0).orient('bottom').tickFormat(context.language.formats.s2); this.xAxis = d3.axisBottom(scale).ticks(5).tickSizeOuter(0).tickFormat(context.language.formats.s2);
this.yAxis = d3.svg.axis().scale(y0).outerTickSize(0).orient('left'); this.yAxis = d3.axisLeft(y0).tickSizeOuter(0);
this.state = { scale, y0, y1, color: d3.scale.ordinal().range(props.colors) }; this.state = { scale, y0, y1, color: d3.scaleOrdinal().range(props.colors) };
} }
/** /**
@@ -131,8 +131,8 @@ export default class BarChart extends TranslatedComponent {
let max = data.reduce((max, build) => (properties.reduce(((m, p) => (m > build[p] ? m : build[p])), max)), 0); let max = data.reduce((max, build) => (properties.reduce(((m, p) => (m > build[p] ? m : build[p])), max)), 0);
this.state.scale.range([0, innerWidth]).domain([0, max]); this.state.scale.range([0, innerWidth]).domain([0, max]);
this.state.y0.domain(data.map(bName)).rangeRoundBands([0, innerHeight], 0.3); this.state.y0.domain(data.map(bName)).range([0, innerHeight], 0.3).padding(0.4);
this.state.y1.domain(properties).rangeRoundBands([0, this.state.y0.rangeBand()]); this.state.y1.domain(properties).range([0, this.state.y0.bandwidth()]).padding(0.1);
this.setState({ this.setState({
barHeight, barHeight,
@@ -192,7 +192,7 @@ export default class BarChart extends TranslatedComponent {
x={0} x={0}
y={y1(p)} y={y1(p)}
width={scale(build[p])} width={scale(build[p])}
height={y1.rangeBand()} height={y1.bandwidth()}
fill={color(p)} fill={color(p)}
onMouseOver={this._showTip.bind(this, build, p, propIndex)} onMouseOver={this._showTip.bind(this, build, p, propIndex)}
onMouseOut={this._hideTip} onMouseOut={this._hideTip}

View File

@@ -0,0 +1,78 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import autoBind from 'auto-bind';
/**
* Boost displays a boost button that toggles bosot
* Requires an onChange() function of the form onChange(boost) which is triggered whenever the boost changes.
*/
export default class Boost extends TranslatedComponent {
static propTypes = {
boost: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props) {
super(props);
autoBind(this);
}
/**
* Add listeners after mounting
*/
componentDidMount() {
document.addEventListener('keydown', this._keyDown);
}
/**
* Remove listeners before unmounting
*/
componentWillUnmount() {
document.removeEventListener('keydown', this._keyDown);
}
/**
* Handle Key Down
* @param {Event} e Keyboard Event
*/
_keyDown(e) {
if (e.ctrlKey || e.metaKey) { // CTRL/CMD
switch (e.keyCode) {
case 66: // b == boost
if (this.props.ship.canBoost()) {
e.preventDefault();
this._toggleBoost();
}
break;
}
}
}
/**
* Toggle the boost feature
*/
_toggleBoost() {
this.props.onChange(!this.props.boost);
}
/**
* Render boost
* @return {React.Component} contents
*/
render() {
const { translate } = this.context.language;
return (
<span id='boost'>
<button id='boost' className={this.props.boost ? 'selected' : null} onClick={this._toggleBoost}>
{translate('boost')}
</button>
</span>
);
}
}

View File

@@ -0,0 +1,75 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import Slider from '../components/Slider';
import autoBind from 'auto-bind';
/**
* Cargo slider
* Requires an onChange() function of the form onChange(cargo), providing the cargo in tonnes, which is triggered on cargo level change
*/
export default class Cargo extends TranslatedComponent {
static propTypes = {
cargo: PropTypes.number.isRequired,
cargoCapacity: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
autoBind(this);
}
/**
* Update cargo level
* @param {number} cargoLevel percentage level from 0 to 1
*/
_cargoChange(cargoLevel) {
const { cargo, cargoCapacity } = this.props;
if (cargoCapacity > 0) {
// We round the cargo to whole number of tonnes
const newCargo = Math.round(cargoLevel * cargoCapacity);
if (newCargo != cargo) {
this.props.onChange(newCargo);
}
}
}
/**
* Render cargo slider
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { cargo, cargoCapacity } = this.props;
return (
<span>
<h3>{translate('cargo carried')}: {formats.int(cargo)}{units.T}</h3>
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
<tbody >
<tr>
<td>
<Slider
axis={true}
onChange={this._cargoChange}
axisUnit={translate('T')}
percent={cargo / cargoCapacity}
max={cargoCapacity}
scale={sizeRatio}
onResize={onWindowResize}
/>
</td>
</tr>
</tbody>
</table>
</span>
);
}
}

View File

@@ -1,21 +1,21 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import Link from './Link'; import Link from './Link';
import cn from 'classnames'; import cn from 'classnames';
import { SizeMap } from '../shipyard/Constants'; import { outfitURL } from '../utils/UrlGenerators';
/** /**
* Comparison Table * Comparison Table
*/ */
export default class ComparisonTable extends TranslatedComponent { export default class ComparisonTable extends TranslatedComponent {
static propTypes = { static propTypes = {
facets: React.PropTypes.array.isRequired, facets: PropTypes.array.isRequired,
builds: React.PropTypes.array.isRequired, builds: PropTypes.array.isRequired,
onSort: React.PropTypes.func.isRequired, onSort: PropTypes.func.isRequired,
predicate: React.PropTypes.string.isRequired, // Used only to test again prop changes for shouldRender predicate: PropTypes.string.isRequired, // Used only to test again prop changes for shouldRender
desc: React.PropTypes.oneOfType([React.PropTypes.bool.isRequired, React.PropTypes.number.isRequired]), // Used only to test again prop changes for shouldRender desc: PropTypes.oneOfType([PropTypes.bool.isRequired, PropTypes.number.isRequired]), // Used only to test again prop changes for shouldRender
}; };
/** /**
@@ -71,21 +71,22 @@ export default class ComparisonTable extends TranslatedComponent {
* @return {React.Component} Table row * @return {React.Component} Table row
*/ */
_buildRow(build, facets, formats, units) { _buildRow(build, facets, formats, units) {
let url = `/outfit/${build.id}/${build.toString()}?bn=${build.buildName}`; if (build && build.id && build.buildName) {
let cells = [ let url = outfitURL(build.id, build.toString(), build.buildName);
<td key='s' className='tl'><Link href={url}>{build.name}</Link></td>, let cells = [
<td key='bn' className='tl'><Link href={url}>{build.buildName}</Link></td> <td key='s' className='tl'><Link href={url}>{build.name}</Link></td>,
]; <td key='bn' className='tl'><Link href={url}>{build.buildName}</Link></td>
];
for (let f of facets) { for (let f of facets) {
if (f.active) { if (f.active) {
for (let p of f.props) { for (let p of f.props) {
cells.push(<td key={p}>{formats[f.fmt](build[p])}{f.unit ? units[f.unit] : null}</td>); cells.push(<td key={p}>{formats[f.fmt](build[p])}{f.unit ? units[f.unit] : null}</td>);
}
} }
} }
return <tr key={build.id + build.buildName} className='tr'>{cells}</tr>;
} }
return <tr key={build.id + build.buildName} className='tr'>{cells}</tr>;
} }
/** /**

View File

@@ -1,21 +1,23 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames'; import cn from 'classnames';
import { Ships } from 'coriolis-data/dist';
import Persist from '../stores/Persist'; import Persist from '../stores/Persist';
import Ship from '../shipyard/Ship'; import { Factory, Ship } from 'ed-forge';
import { Insurance } from '../shipyard/Constants'; import { Insurance } from '../shipyard/Constants';
import { slotName, slotComparator } from '../utils/SlotFunctions';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import { ShoppingIcon } from './SvgIcons';
import autoBind from 'auto-bind';
import { assign, differenceBy, sortBy, reverse } from 'lodash';
import { FUEL_CAPACITY } from 'ed-forge/lib/src/ship-stats';
/** /**
* Cost Section * Cost Section
*/ */
export default class CostSection extends TranslatedComponent { export default class CostSection extends TranslatedComponent {
static propTypes = {
static PropTypes = { ship: PropTypes.object.isRequired,
ship: React.PropTypes.object.isRequired, code: PropTypes.string.isRequired,
code: React.PropTypes.string.isRequired, buildName: PropTypes.string,
buildName: React.PropTypes.string
}; };
/** /**
@@ -24,70 +26,34 @@ export default class CostSection extends TranslatedComponent {
*/ */
constructor(props) { constructor(props) {
super(props); super(props);
this._costsTab = this._costsTab.bind(this); autoBind(this);
this._sortCost = this._sortCost.bind(this);
this._sortAmmo = this._sortAmmo.bind(this);
this._sortRetrofit = this._sortRetrofit.bind(this);
this._buildRetrofitShip = this._buildRetrofitShip.bind(this);
this._onBaseRetrofitChange = this._onBaseRetrofitChange.bind(this);
this._defaultRetrofitName = this._defaultRetrofitName.bind(this);
let data = Ships[props.ship.id]; // Retrieve the basic ship properties, slots and defaults
let retrofitName = this._defaultRetrofitName(props.ship.id, props.buildName);
let retrofitShip = this._buildRetrofitShip(props.ship.id, retrofitName);
let shipDiscount = Persist.getShipDiscount();
let moduleDiscount = Persist.getModuleDiscount();
this.props.ship.applyDiscounts(shipDiscount, moduleDiscount);
retrofitShip.applyDiscounts(shipDiscount, moduleDiscount);
const { ship, buildName } = props;
this.shipType = ship.getShipType();
this.state = { this.state = {
retrofitShip, retrofitName: Persist.hasBuild(ship.getShipType(), buildName) ? buildName : null,
retrofitName, shipDiscount: Persist.getShipDiscount(),
shipDiscount, moduleDiscount: Persist.getModuleDiscount(),
moduleDiscount,
insurance: Insurance[Persist.getInsurance()], insurance: Insurance[Persist.getInsurance()],
tab: Persist.getCostTab(), tab: Persist.getCostTab(),
buildOptions: Persist.getBuildsNamesFor(props.ship.id), buildOptions: Persist.getBuildsNamesFor(ship.getShipType()),
ammoPredicate: 'cr', predicate: 'cr',
ammoDesc: true, desc: true,
costPredicate: 'cr', excluded: {},
costDesc: true,
retroPredicate: 'cr',
retroDesc: true
}; };
} }
/** /**
* Create a ship instance to base/reference retrofit changes from * Create a ship instance to base/reference retrofit changes from
* @param {string} shipId Ship Id
* @param {string} name Build name
* @param {Ship} retrofitShip Existing retrofit ship
* @return {Ship} Retrofit ship * @return {Ship} Retrofit ship
*/ */
_buildRetrofitShip(shipId, name, retrofitShip) { _buildRetrofitShip() {
let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults const { retrofitName } = this.state;
if (Persist.hasBuild(this.shipType, retrofitName)) {
if (!retrofitShip) { // Don't create a new instance unless needed return new Ship(Persist.getBuild(this.shipType, retrofitName));
retrofitShip = new Ship(shipId, data.properties, data.slots); // Create a new Ship for retrofit comparison
}
if (Persist.hasBuild(shipId, name)) {
retrofitShip.buildFrom(Persist.getBuild(shipId, name)); // Populate modules from existing build
} else { } else {
retrofitShip.buildWith(data.defaults); // Populate with default components return Factory.newShip(this.shipType);
} }
return retrofitShip;
}
/**
* Get the default retrofit build name if it exists
* @param {string} shipId Ship Id
* @param {string} name Build name
* @return {string} Build name or null
*/
_defaultRetrofitName(shipId, name) {
return Persist.hasBuild(shipId, name) ? name : null;
} }
/** /**
@@ -105,9 +71,6 @@ export default class CostSection extends TranslatedComponent {
_onDiscountChanged() { _onDiscountChanged() {
let shipDiscount = Persist.getShipDiscount(); let shipDiscount = Persist.getShipDiscount();
let moduleDiscount = Persist.getModuleDiscount(); let moduleDiscount = Persist.getModuleDiscount();
this.props.ship.applyDiscounts(shipDiscount, moduleDiscount);
this.state.retrofitShip.applyDiscounts(shipDiscount, moduleDiscount);
this._updateRetrofit(this.props.ship, this.state.retrofitShip);
this.setState({ shipDiscount, moduleDiscount }); this.setState({ shipDiscount, moduleDiscount });
} }
@@ -124,156 +87,33 @@ export default class CostSection extends TranslatedComponent {
* @param {SyntheticEvent} event Build name to base the retrofit ship on * @param {SyntheticEvent} event Build name to base the retrofit ship on
*/ */
_onBaseRetrofitChange(event) { _onBaseRetrofitChange(event) {
let retrofitName = event.target.value; this.setState({ retrofitName: event.target.value });
let ship = this.props.ship;
if (retrofitName) {
this.state.retrofitShip.buildFrom(Persist.getBuild(ship.id, retrofitName));
} else {
this.state.retrofitShip.buildWith(Ships[ship.id].defaults); // Retrofit ship becomes stock build
}
this._updateRetrofit(ship, this.state.retrofitShip);
this.setState({ retrofitName });
} }
/** /**
* On builds changed check to see if the retrofit ship needs * Toggle item cost inclusion
* to be updated * @param {String} key Key of the row to toggle
*/ */
_onBuildsChanged() { _toggleExcluded(key) {
let update = false; let { excluded } = this.state;
let ship = this.props.ship; excluded = assign({}, excluded);
let { retrofitName, retrofitShip } = this.state; const slotExcluded = excluded[key];
excluded[key] = (slotExcluded === undefined ? true : !slotExcluded);
if(!Persist.hasBuild(ship.id, retrofitName)) { this.setState({ excluded });
retrofitShip.buildWith(Ships[ship.id].defaults); // Retrofit ship becomes stock build
this.setState({ retrofitName: null });
update = true;
} else if (Persist.getBuild(ship.id, retrofitName) != retrofitShip.toString()) {
retrofitShip.buildFrom(Persist.getBuild(ship.id, retrofitName)); // Repopulate modules from saved build
update = true;
}
if (update) { // Update retrofit comparison
this._updateRetrofit(ship, retrofitShip);
}
// Update list of retrofit base build options
this.setState({ buildOptions: Persist.getBuildsNamesFor(ship.id) });
} }
/** /**
* Toggle item cost inclusion in overall total * Set list sort predicate
* @param {Object} item Cost item * @param {string} newPredicate sort predicate
*/ */
_toggleCost(item) { _sortBy(newPredicate) {
this.props.ship.setCostIncluded(item, !item.incCost); let { predicate, desc } = this.state;
this.forceUpdate();
}
/** if (newPredicate == predicate) {
* Toggle item cost inclusion in retrofit total desc = !desc;
* @param {Object} item Cost item
*/
_toggleRetrofitCost(item) {
let retrofitTotal = this.state.retrofitTotal;
item.retroItem.incCost = !item.retroItem.incCost;
retrofitTotal += item.netCost * (item.retroItem.incCost ? 1 : -1);
this.setState({ retrofitTotal });
}
/**
* Set cost list sort predicate
* @param {string} predicate sort predicate
*/
_sortCostBy(predicate) {
let { costPredicate, costDesc } = this.state;
if (costPredicate == predicate) {
costDesc = !costDesc;
} }
this.setState({ costPredicate: predicate, costDesc }); this.setState({ predicate: newPredicate, desc });
}
/**
* Sort cost list
* @param {Ship} ship Ship instance
* @param {string} predicate Sort predicate
* @param {Boolean} desc Sort descending
*/
_sortCost(ship, predicate, desc) {
let costList = ship.costList;
let translate = this.context.language.translate;
if (predicate == 'm') {
costList.sort(slotComparator(translate, null, desc));
} else {
costList.sort(slotComparator(translate, (a, b) => (a.m.cost || 0) - (b.m.cost || 0), desc));
}
}
/**
* Set ammo list sort predicate
* @param {string} predicate sort predicate
*/
_sortAmmoBy(predicate) {
let { ammoPredicate, ammoDesc } = this.state;
if (ammoPredicate == predicate) {
ammoDesc = !ammoDesc;
}
this.setState({ ammoPredicate: predicate, ammoDesc });
}
/**
* Sort ammo cost list
* @param {Array} ammoCosts Ammo cost list
* @param {string} predicate Sort predicate
* @param {Boolean} desc Sort descending
*/
_sortAmmo(ammoCosts, predicate, desc) {
let translate = this.context.language.translate;
if (predicate == 'm') {
ammoCosts.sort(slotComparator(translate, null, desc));
} else {
ammoCosts.sort(slotComparator(translate, (a, b) => a[predicate] - b[predicate], desc));
}
}
/**
* Set retrofit list sort predicate
* @param {string} predicate sort predicate
*/
_sortRetrofitBy(predicate) {
let { retroPredicate, retroDesc } = this.state;
if (retroPredicate == predicate) {
retroDesc = !retroDesc;
}
this.setState({ retroPredicate: predicate, retroDesc });
}
/**
* Sort retrofit cost list
* @param {Array} retrofitCosts Retrofit cost list
* @param {string} predicate Sort predicate
* @param {Boolean} desc Sort descending
*/
_sortRetrofit(retrofitCosts, predicate, desc) {
let translate = this.context.language.translate;
if (predicate == 'cr') {
retrofitCosts.sort((a, b) => a.netCost - b.netCost);
} else {
retrofitCosts.sort((a , b) => (a[predicate] ? translate(a[predicate]).toLowerCase() : '').localeCompare(b[predicate] ? translate(b[predicate]).toLowerCase() : ''));
}
if (!desc) {
retrofitCosts.reverse();
}
} }
/** /**
@@ -282,18 +122,34 @@ export default class CostSection extends TranslatedComponent {
*/ */
_costsTab() { _costsTab() {
let { ship } = this.props; let { ship } = this.props;
let { shipDiscount, moduleDiscount, insurance } = this.state; let {
excluded, shipDiscount, moduleDiscount, insurance, desc, predicate
} = this.state;
let { translate, formats, units } = this.context.language; let { translate, formats, units } = this.context.language;
let rows = []; let rows = [];
for (let i = 0, l = ship.costList.length; i < l; i++) { let modules = sortBy(
let item = ship.costList[i]; ship.getModules(),
if (item.m && item.m.cost) { (predicate === 'm' ? (m) => m.getItem() : (m) => m.readMeta('cost'))
let toggle = this._toggleCost.bind(this, item); );
rows.push(<tr key={i} className={cn('highlight', { disabled: !item.incCost })}> if (desc) {
<td className='ptr' style={{ width: '1em' }} onClick={toggle}>{item.m.class + item.m.rating}</td> reverse(modules);
<td className='le ptr shorten cap' onClick={toggle}>{slotName(translate, item)}</td> }
<td className='ri ptr' onClick={toggle}>{formats.int(item.discountedCost)}{units.CR}</td>
let totalCost = 0;
for (let module of modules) {
const cost = module.readMeta('cost');
const slot = module.getSlot();
if (cost) {
let toggle = this._toggleExcluded.bind(this, slot);
const disabled = excluded[slot];
if (!disabled) {
totalCost += cost;
}
rows.push(<tr key={slot} className={cn('highlight', { disabled })}>
<td className='ptr' style={{ width: '1em' }} onClick={toggle}>{module.getClassRating()}</td>
<td className='le ptr shorten cap' onClick={toggle}>{translate(module.readMeta('type'))}</td>
<td className='ri ptr' onClick={toggle}>{formats.int(cost * (1 - moduleDiscount))}{units.CR}</td>
</tr>); </tr>);
} }
} }
@@ -302,85 +158,143 @@ export default class CostSection extends TranslatedComponent {
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr className='main'> <tr className='main'>
<th colSpan='2' className='sortable le' onClick={this._sortCostBy.bind(this,'m')}> <th colSpan='2' className='sortable le' onClick={() => this._sortBy('m')}>
{translate('module')} {translate('module')}
{shipDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct(shipDiscount)}]`}</u> : null} {shipDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} ${formats.pct(-1 * shipDiscount)}]`}</u> : null}
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null} {moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} ${formats.pct(-1 * moduleDiscount)}]`}</u> : null}
</th> </th>
<th className='sortable le' onClick={this._sortCostBy.bind(this, 'cr')} >{translate('credits')}</th> <th className='sortable le' onClick={() => this._sortBy('cr')} >{translate('credits')}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{rows} {rows}
<tr className='ri'> <tr className='ri'>
<td colSpan='2' className='lbl' >{translate('total')}</td> <td colSpan='2' className='lbl' >{translate('total')}</td>
<td className='val'>{formats.int(ship.totalCost)}{units.CR}</td> <td className='val'>{formats.int(totalCost)}{units.CR}</td>
</tr> </tr>
<tr className='ri'> <tr className='ri'>
<td colSpan='2' className='lbl'>{translate('insurance')}</td> <td colSpan='2' className='lbl'>{translate('insurance')}</td>
<td className='val'>{formats.int(ship.totalCost * insurance)}{units.CR}</td> <td className='val'>{formats.int(totalCost * insurance)}{units.CR}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div>; </div>;
} }
/**
* Open up a window for EDDB with a shopping list of our retrofit components
*/
_eddbShoppingList() {
const {} = this.state;
const { ship } = this.props;
// Provide unique list of non-PP module EDDB IDs to buy
// const modIds = retrofitCosts.filter(item => item.retroItem.incCost && item.buyId && !item.buyPp).map(item => item.buyId).filter((v, i, a) => a.indexOf(v) === i);
// Open up the relevant URL
// TODO:
// window.open('https://eddb.io/station?m=' + modIds.join(','));
}
/**
*
*/
_retrofitInfo() {
const { ship } = this.props;
const { desc, moduleDiscount, predicate, retrofitName, excluded } = this.state;
const retrofitShip = this._buildRetrofitShip();
const currentModules = ship.getModules();
const oldModules = retrofitShip.getModules();
const buyModules = differenceBy(currentModules, oldModules, (m) => m.getItem());
const sellModules = differenceBy(oldModules, currentModules, (m) => m.getItem());
let modules = [];
let totalCost = 0;
const addModule = (m, costFactor) => {
const key = `${m.getItem()}@${m.getSlot()}`;
const cost = costFactor * m.readMeta('cost') * (1 - moduleDiscount);
modules.push({
key, cost,
rating: m.getClassRating(),
item: m.readMeta('type'),
});
if (!excluded[key]) {
totalCost += cost;
}
};
for (let m of buyModules) {
addModule(m, 1);
}
for (let m of sellModules) {
addModule(m, -1);
}
let _sortF = undefined;
switch (predicate) {
case 'cr': _sortF = (o) => o.cost; break;
case 'm':
default: _sortF = (o) => o.item; break;
};
modules = sortBy(modules, _sortF);
if (desc) {
reverse(modules);
}
return [totalCost, modules];
}
/** /**
* Render the retofit tab * Render the retofit tab
* @return {React.Component} Tab contents * @return {React.Component} Tab contents
*/ */
_retrofitTab() { _retrofitTab() {
let { retrofitTotal, retrofitCosts, moduleDiscount, retrofitName } = this.state; let { buildOptions, excluded, moduleDiscount, retrofitName } = this.state;
const { termtip, tooltip } = this.context;
let { translate, formats, units } = this.context.language; let { translate, formats, units } = this.context.language;
let int = formats.int; let int = formats.int;
let rows = [], options = [<option key='stock' value=''>{translate('Stock')}</option>];
for (let opt of this.state.buildOptions) {
options.push(<option key={opt} value={opt}>{opt}</option>);
}
if (retrofitCosts.length) {
for (let i = 0, l = retrofitCosts.length; i < l; i++) {
let item = retrofitCosts[i];
rows.push(<tr key={i} className={cn('highlight', { disabled: !item.retroItem.incCost })} onClick={this._toggleRetrofitCost.bind(this, item)}>
<td className='ptr' style={{ width: '1em' }}>{item.sellClassRating}</td>
<td className='le ptr shorten cap'>{translate(item.sellName)}</td>
<td className='ptr' style={{ width: '1em' }}>{item.buyClassRating}</td>
<td className='le ptr shorten cap'>{translate(item.buyName)}</td>
<td colSpan='2' className={cn('ri ptr', item.retroItem.incCost ? item.netCost > 0 ? 'warning' : 'secondary-disabled' : 'disabled')}>{int(item.netCost)}{units.CR}</td>
</tr>);
}
} else {
rows = <tr><td colSpan='7' style={{ padding: '3em 0' }}>{translate('PHRASE_NO_RETROCH')}</td></tr>;
}
const [retrofitTotal, retrofitInfo] = this._retrofitInfo();
return <div> return <div>
<div className='scroll-x'> <div className='scroll-x'>
<table style={{ width: '100%' }}> <table style={{ width: '100%' }}>
<thead> <thead>
<tr className='main'> <tr className='main'>
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'sellName')}>{translate('sell')}</th> <th colSpan='2' className='sortable le' onClick={() => this._sortBy('m')}>{translate('module')}</th>
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'buyName')}>{translate('buy')}</th> <th colSpan='2' className='sortable le' onClick={() => this._sortBy('cr')}>
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'cr')}>
{translate('net cost')} {translate('net cost')}
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null} {moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{rows} {retrofitInfo.length ?
retrofitInfo.map((info) => (
<tr key={info.key} className={cn('highlight', { disabled: excluded[info.key] })}
onClick={() => this._toggleExcluded(info.key)}>
<td className='ptr' style={{ width: '1em' }}>{info.rating}</td>
<td className='le ptr shorten cap'>{translate(info.item)}</td>
<td colSpan="2" className={cn('ri ptr', excluded[info.key] ? 'disabled' : (info.cost < 0 ? 'secondary-disabled' : 'warning'))}>
{int(info.cost)}{units.CR}
</td>
</tr>
)) : (
<tr><td colSpan='7' style={{ padding: '3em 0' }}>{translate('PHRASE_NO_RETROCH')}</td></tr>
)}
<tr className='ri'> <tr className='ri'>
<td colSpan='4' className='lbl' >{translate('cost')}</td> <td className='lbl' ><button onClick={this._eddbShoppingList} onMouseOver={termtip.bind(null, 'PHRASE_REFIT_SHOPPING_LIST')} onMouseOut={tooltip.bind(null, null)}><ShoppingIcon className='lg' style={{ fill: 'black' }}/></button></td>
<td className='lbl' >{translate('cost')}</td>
<td colSpan='2' className={cn('val', retrofitTotal > 0 ? 'warning' : 'secondary-disabled')} style={{ borderBottom:'none' }}> <td colSpan='2' className={cn('val', retrofitTotal > 0 ? 'warning' : 'secondary-disabled')} style={{ borderBottom:'none' }}>
{int(retrofitTotal)}{units.CR} {int(retrofitTotal)}{units.CR}
</td> </td>
</tr> </tr>
<tr className='ri'> <tr className='ri'>
<td colSpan='4' className='lbl cap' >{translate('retrofit from')}</td> <td colSpan='2' className='lbl cap' >{translate('retrofit from')}</td>
<td className='val cen' style={{ borderRight: 'none', width: '1em' }}><u className='primary-disabled'>&#9662;</u></td> <td className='val cen' style={{ borderRight: 'none', width: '1em' }}><u className='primary-disabled'>&#9662;</u></td>
<td className='val' style={{ borderLeft:'none', padding: 0 }}> <td className='val' style={{ borderLeft:'none', padding: 0 }}>
<select style={{ width: '100%', padding: 0 }} value={retrofitName} onChange={this._onBaseRetrofitChange}> <select style={{ width: '100%', padding: 0 }} value={retrofitName || translate('Stock')} onChange={this._onBaseRetrofitChange}>
{options} <option key='stock' value=''>{translate('Stock')}</option>
{buildOptions.map((opt) => <option key={opt} value={opt}>{opt}</option>)}
</select> </select>
</td> </td>
</tr> </tr>
@@ -390,57 +304,50 @@ export default class CostSection extends TranslatedComponent {
</div>; </div>;
} }
/** /**
* Update retrofit costs *
* @param {Ship} ship Ship instance * @param {*} modules
* @param {Ship} retrofitShip Retrofit Ship instance
*/ */
_updateRetrofit(ship, retrofitShip) { _ammoInfo() {
let retrofitCosts = []; const { ship } = this.props;
let retrofitTotal = 0, i, l, item; const { desc, predicate } = this.state;
if (ship.bulkheads.m.index != retrofitShip.bulkheads.m.index) { let info = [{
item = { key: 'fuel',
buyClassRating: ship.bulkheads.m.class + ship.bulkheads.m.rating, item: 'Fuel',
buyName: ship.bulkheads.m.name, qty: ship.get(FUEL_CAPACITY),
sellClassRating: retrofitShip.bulkheads.m.class + retrofitShip.bulkheads.m.rating, unitCost: 50,
sellName: retrofitShip.bulkheads.m.name, cost: 50 * ship.get(FUEL_CAPACITY),
netCost: ship.bulkheads.discountedCost - retrofitShip.bulkheads.discountedCost, }];
retroItem: retrofitShip.bulkheads for (let m of ship.getModules()) {
}; const rebuilds = m.get('bays') * m.get('rebuildsperbay');
retrofitCosts.push(item); const ammo = (m.get('ammomaximum') + m.get('ammoclipsize')) || rebuilds;
if (retrofitShip.bulkheads.incCost) { if (ammo) {
retrofitTotal += item.netCost; const unitCost = m.readMeta('ammocost');
info.push({
key: `restock_${m.getSlot()}`,
rating: m.getClassRating(),
item: m.readMeta('type'),
qty: ammo,
unitCost, cost: unitCost * ammo,
});
} }
} }
for (let g in { standard: 1, internal: 1, hardpoints: 1 }) { let _sortF = undefined;
let retroSlotGroup = retrofitShip[g]; switch (predicate) {
let slotGroup = ship[g]; case 'cr': _sortF = (o) => o.cost; break;
for (i = 0, l = slotGroup.length; i < l; i++) { case 'qty': _sortF = (o) => o.qty; break;
if (slotGroup[i].m != retroSlotGroup[i].m) { case 'cost': _sortF = (o) => o.unitCost; break;
item = { netCost: 0, retroItem: retroSlotGroup[i] }; case 'm':
if (slotGroup[i].m) { default: _sortF = (o) => o.item;
item.buyName = slotGroup[i].m.name || slotGroup[i].m.grp; }
item.buyClassRating = slotGroup[i].m.class + slotGroup[i].m.rating; info = sortBy(info, _sortF);
item.netCost = slotGroup[i].discountedCost; if (desc) {
} reverse(info);
if (retroSlotGroup[i].m) {
item.sellName = retroSlotGroup[i].m.name || retroSlotGroup[i].m.grp;
item.sellClassRating = retroSlotGroup[i].m.class + retroSlotGroup[i].m.rating;
item.netCost -= retroSlotGroup[i].discountedCost;
}
retrofitCosts.push(item);
if (retroSlotGroup[i].incCost) {
retrofitTotal += item.netCost;
}
}
}
} }
this.setState({ retrofitCosts, retrofitTotal }); return info;
this._sortRetrofit(retrofitCosts, this.state.retroPredicate, this.state.retroDesc);
} }
/** /**
@@ -448,20 +355,24 @@ export default class CostSection extends TranslatedComponent {
* @return {React.Component} Tab contents * @return {React.Component} Tab contents
*/ */
_ammoTab() { _ammoTab() {
let { ammoTotal, ammoCosts } = this.state; const { excluded } = this.state;
let { translate, formats, units } = this.context.language; const { translate, formats, units } = this.context.language;
let int = formats.int; const int = formats.int;
let rows = []; const rows = [];
for (let i = 0, l = ammoCosts.length; i < l; i++) { const ammoInfo = this._ammoInfo();
let item = ammoCosts[i]; let total = 0;
rows.push(<tr key={i} className='highlight'> for (let i of ammoInfo) {
<td style={{ width: '1em' }}>{item.m.class + item.m.rating}</td> const disabled = excluded[i.key];
<td className='le shorten cap'>{slotName(translate, item)}</td> rows.push(<tr key={i.key} onClick={() => this._toggleExcluded(i.key)}
<td className='ri'>{int(item.max)}</td> className={cn('highlight', { disabled })}>
<td className='ri'>{int(item.cost)}{units.CR}</td> <td style={{ width: '1em' }}>{i.rating}</td>
<td className='ri'>{int(item.total)}{units.CR}</td> <td className='le shorten cap'>{translate(i.item)}</td>
<td className='ri'>{int(i.qty)}</td>
<td className='ri'>{int(i.unitCost)}{units.CR}</td>
<td className='ri'>{int(i.cost)}{units.CR}</td>
</tr>); </tr>);
total += disabled ? 0 : i.cost;
} }
return <div> return <div>
@@ -469,17 +380,17 @@ export default class CostSection extends TranslatedComponent {
<table style={{ width: '100%' }}> <table style={{ width: '100%' }}>
<thead> <thead>
<tr className='main'> <tr className='main'>
<th colSpan='2' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'm')} >{translate('module')}</th> <th colSpan='2' className='sortable le' onClick={() => this._sortBy('m')}>{translate('module')}</th>
<th colSpan='1' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'max')} >{translate('qty')}</th> <th colSpan='1' className='sortable le' onClick={() => this._sortBy('qty')}>{translate('qty')}</th>
<th colSpan='1' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'cost')} >{translate('unit cost')}</th> <th colSpan='1' className='sortable le' onClick={() => this._sortBy('cost')}>{translate('unit cost')}</th>
<th className='sortable le' onClick={this._sortAmmoBy.bind(this, 'total')}>{translate('subtotal')}</th> <th className='sortable le' onClick={() => this._sortBy('cr')}>{translate('subtotal')}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{rows} {rows}
<tr className='ri'> <tr className='ri'>
<td colSpan='4' className='lbl' >{translate('total')}</td> <td colSpan='4' className='lbl' >{translate('total')}</td>
<td className='val'>{int(ammoTotal)}{units.CR}</td> <td className='val'>{int(total)}{units.CR}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -487,91 +398,6 @@ export default class CostSection extends TranslatedComponent {
</div>; </div>;
} }
/**
* Recalculate all ammo costs
* @param {Ship} ship Ship instance
*/
_updateAmmoCosts(ship) {
let ammoCosts = [], ammoTotal = 0, item, q, limpets = 0, srvs = 0, scoop = false;
for (let g in { standard: 1, internal: 1, hardpoints: 1 }) {
let slotGroup = ship[g];
for (let i = 0, l = slotGroup.length; i < l; i++) {
if (slotGroup[i].m) {
// Special cases needed for SCB, AFMU, and limpet controllers since they don't use standard ammo/clip
q = 0;
switch (slotGroup[i].m.grp) {
case 'fs': // Skip fuel calculation if scoop present
scoop = true;
break;
case 'scb':
q = slotGroup[i].m.cells;
break;
case 'am':
q = slotGroup[i].m.ammo;
break;
case 'pv':
srvs += slotGroup[i].m.vehicles;
break;
case 'fx': case 'hb': case 'cc': case 'pc':
limpets = ship.cargoCapacity;
break;
default:
q = slotGroup[i].m.clip + slotGroup[i].m.ammo;
}
// Calculate ammo costs only if a cost is specified
if (slotGroup[i].m.ammocost > 0) {
item = {
m: slotGroup[i].m,
max: q,
cost: slotGroup[i].m.ammocost,
total: q * slotGroup[i].m.ammocost
};
ammoCosts.push(item);
ammoTotal += item.total;
}
}
}
}
// Limpets if controllers exist and cargo space available
if (limpets > 0) {
item = {
m: { name: 'limpets', class: '', rating: '' },
max: ship.cargoCapacity,
cost: 101,
total: ship.cargoCapacity * 101
};
ammoCosts.push(item);
ammoTotal += item.total;
}
if (srvs > 0) {
item = {
m: { name: 'SRVs', class: '', rating: '' },
max: srvs,
cost: 6005,
total: srvs * 6005
};
ammoCosts.push(item);
ammoTotal += item.total;
}
// Calculate refuel costs if no scoop present
if (!scoop) {
item = {
m: { name: 'fuel', class: '', rating: '' },
max: ship.fuelCapacity,
cost: 50,
total: ship.fuelCapacity * 50
};
ammoCosts.push(item);
ammoTotal += item.total;
}
this.setState({ ammoTotal, ammoCosts });
this._sortAmmo(ammoCosts, this.state.ammoPredicate, this.state.ammoDesc);
}
/** /**
* Add listeners on mount and update costs * Add listeners on mount and update costs
*/ */
@@ -579,63 +405,7 @@ export default class CostSection extends TranslatedComponent {
this.listeners = [ this.listeners = [
Persist.addListener('discounts', this._onDiscountChanged.bind(this)), Persist.addListener('discounts', this._onDiscountChanged.bind(this)),
Persist.addListener('insurance', this._onInsuranceChanged.bind(this)), Persist.addListener('insurance', this._onInsuranceChanged.bind(this)),
Persist.addListener('builds', this._onBuildsChanged.bind(this)),
]; ];
this._updateAmmoCosts(this.props.ship);
this._updateRetrofit(this.props.ship, this.state.retrofitShip);
this._sortCost(this.props.ship);
}
/**
* Update state based on property and context changes
* @param {Object} nextProps Incoming/Next properties
* @param {Object} nextContext Incoming/Next context
*/
componentWillReceiveProps(nextProps, nextContext) {
let retrofitShip = this.state.retrofitShip;
if (nextProps.ship != this.props.ship) { // Ship has changed
let nextId = nextProps.ship.id;
let retrofitName = this._defaultRetrofitName(nextId, nextProps.buildName);
retrofitShip = this._buildRetrofitShip(nextId, retrofitName, nextId == this.props.ship.id ? retrofitShip : null);
this.setState({
retrofitShip,
retrofitName,
buildOptions: Persist.getBuildsNamesFor(nextId)
});
}
if (nextProps.ship != this.props.ship || nextProps.code != this.props.code) {
this._updateAmmoCosts(nextProps.ship);
this._updateRetrofit(nextProps.ship, retrofitShip);
this._sortCost(nextProps.ship);
}
}
/**
* Sort lists before render
* @param {Object} nextProps Incoming/Next properties
* @param {Object} nextState Incoming/Next state
*/
componentWillUpdate(nextProps, nextState) {
let state = this.state;
switch (nextState.tab) {
case 'ammo':
if (state.ammoPredicate != nextState.ammoPredicate || state.ammoDesc != nextState.ammoDesc) {
this._sortAmmo(nextState.ammoCosts, nextState.ammoPredicate, nextState.ammoDesc);
}
break;
case 'retrofit':
if (state.retroPredicate != nextState.retroPredicate || state.retroDesc != nextState.retroDesc) {
this._sortRetrofit(nextState.retrofitCosts, nextState.retroPredicate, nextState.retroDesc);
}
break;
default:
if (state.costPredicate != nextState.costPredicate || state.costDesc != nextState.costDesc) {
this._sortCost(nextProps.ship, nextState.costPredicate, nextState.costDesc);
}
}
} }
/** /**

View File

@@ -0,0 +1,175 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import PieChart from './PieChart';
import VerticalBarChart from './VerticalBarChart';
import autoBind from 'auto-bind';
import { ARMOUR_METRICS, MODULE_PROTECTION_METRICS, SHIELD_METRICS } from 'ed-forge/lib/src/ship-stats';
/**
* Defence information
* Shield information consists of four panels:
* - textual information (time to lose shields etc.)
* - breakdown of shield sources (pie chart)
* - comparison of shield resistances (bar chart)
* - effective shield (bar chart)
*/
export default class Defence extends TranslatedComponent {
static propTypes = {
code: PropTypes.string.isRequired,
ship: PropTypes.object.isRequired,
opponent: PropTypes.object.isRequired,
engagementRange: PropTypes.number.isRequired,
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
autoBind(this);
}
/**
* Render defence
* @return {React.Component} contents
*/
render() {
const { ship } = this.props;
const { language, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const shields = ship.get(SHIELD_METRICS);
// Data for pie chart (absolute MJ)
const shieldSourcesData = [
'byBoosters', 'byGenerator', 'byReinforcements', 'bySCBs',
].map((key) => { return { label: key, value: Math.round(shields[key]) }; });
// Data for tooltip
const shieldSourcesTt = shieldSourcesData.map((o) => {
let { label, value } = o;
return <div key={label}>
{translate(label)} {formats.int(value)}{units.MJ}
</div>;
});
// Shield resistances
const shieldDamageTakenData = [
'absolute', 'explosive', 'kinetic', 'thermal',
].map((label) => {
const dmgMult = shields[label];
const tooltip = ['byBoosters', 'byGenerator', 'bySys'].map(
(label) => <div key={label}>
{translate(label)} {formats.pct1(dmgMult[label])}
</div>
);
return { label, value: Math.round(100 * dmgMult.withSys), tooltip };
});
// Effective MJ
const effectiveShieldData = [
'absolute', 'explosive', 'kinetic', 'thermal'
].map((label) => {
const dmgMult = shields[label];
const raw = shields.withSCBs;
const tooltip = ['byBoosters', 'byGenerator', 'bySys'].map(
(label) => <div key={label}>
{translate(label)} {formats.int(raw * dmgMult[label])}{units.MJ}
</div>
);
return { label, value: Math.round(dmgMult.withSys * raw), tooltip };
});
const maxEffectiveShield = Math.max(...effectiveShieldData.map((o) => o.value));
const armour = ship.get(ARMOUR_METRICS);
const moduleProtection = ship.get(MODULE_PROTECTION_METRICS);
// Data for pie chart (absolute HP)
const armourSourcesData = ['base', 'byAlloys', 'byHRPs',].map(
(key) => { return { label: key, value: Math.round(armour[key]) }; }
);
// Data for tooltip
const armourSourcesTt = armourSourcesData.map((o) => {
let { label, value } = o;
return <div key={label}>{translate(label)} {formats.int(value)}</div>;
});
// Armour resistances
const armourDamageTakenData = [
'absolute', 'explosive', 'kinetic', 'thermal', 'caustic',
].map((label) => {
const dmgMult = armour[label];
const tooltip = ['byAlloys', 'byHRPs'].map(
(label) => <div key={label}>
{translate(label)} {formats.pct1(dmgMult[label])}
</div>
);
return { label, value: Math.round(100 * dmgMult.damageMultiplier), tooltip };
});
// Effective HP
const effectiveArmourData = [
'absolute', 'explosive', 'kinetic', 'thermal'
].map((label) => {
const dmgMult = armour[label];
const raw = armour.armour;
const tooltip = ['byBoosters', 'byGenerator', 'bySys'].map(
(label) => <div key={label}>
{translate(label)} {formats.int(raw * dmgMult[label])}
</div>
);
return { label, value: Math.round(dmgMult.damageMultiplier * raw), tooltip };
});
return (
<span id='defence'>
{shields.withSCBs ? <span>
<div className='group quarter'>
<h2>{translate('shield metrics')}</h2>
<br/>
<h2 onMouseOver={termtip.bind(null, <div>{shieldSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')}<br/>{formats.int(shields.withSCBs)}{units.MJ}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_SHIELDS')}<br/>TODO</h2>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECOVER'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECOVER_SHIELDS')}<br/>{shields.recover ? formats.time(shields.recover) : translate('never')}</h2>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECHARGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')}<br/>{shields.recharge ? formats.time(shields.recharge) : translate('never')}</h2>
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield sources')}</h2>
<PieChart data={shieldSourcesData} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_DAMAGE_TAKEN'))} onMouseOut={tooltip.bind(null, null)}>{translate('damage taken')}(%)</h2>
<VerticalBarChart data={shieldDamageTakenData} yMax={140} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_SHIELD'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective shield')}(MJ)</h2>
<VerticalBarChart data={effectiveShieldData} yMax={maxEffectiveShield}/>
</div>
</span> : null }
<div className='group quarter'>
<h2>{translate('armour metrics')}</h2>
<h2 onMouseOver={termtip.bind(null, <div>{armourSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw armour strength')}<br/>{formats.int(armour.armour)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_ARMOUR')}<br/>TODO</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('raw module armour')}<br/>{formats.int(moduleProtection.moduleArmour)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_EXTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_EXTERNAL')}<br/>{formats.pct1((1 - moduleProtection.moduleProtection) / 2)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_INTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_INTERNAL')}<br/>{formats.pct1(1 - moduleProtection.moduleProtection)}</h2>
<br/>
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_ARMOUR_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('armour sources')}</h2>
<PieChart data={armourSourcesData} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_DAMAGE_TAKEN'))} onMouseOut={tooltip.bind(null, null)}>{translate('damage taken')}(%)</h2>
<VerticalBarChart data={armourDamageTakenData} yMax={100} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective armour')}</h2>
<VerticalBarChart data={effectiveArmourData} />
</div>
</span>);
}
}

View File

@@ -0,0 +1,134 @@
import React from 'react';
import autoBind from 'auto-bind';
import Persist from '../stores/Persist';
import PropTypes from 'prop-types';
import { getBlueprintUuid, getExperimentalUuid } from 'ed-forge/lib/src/data/blueprints';
import { Loader, MatIcon } from '../components/SvgIcons';
import request from 'superagent';
import { chain, entries } from 'lodash';
import TranslatedComponent from './TranslatedComponent';
const STATE = {
READY: 0,
LOADING: 1,
ERROR: 2,
DONE: 3,
};
/**
*
*/
export default class EDEngineerButton extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
autoBind(this);
const { ship } = props;
const uuids = chain(ship.getModules())
.filter((m) => m.getBlueprint())
.map((m) => {
const uuids = [getBlueprintUuid(m.getBlueprint(), m.getBlueprintGrade())];
const exp = m.getExperimental();
if (exp) {
uuids.push(getExperimentalUuid(exp));
}
return uuids;
})
.flatMap()
.groupBy()
.mapValues((v) => v.length)
.value();
this.state = {
status: STATE.READY,
uuids,
};
}
/**
* Generates the shopping list
*/
_sendToEDEngineer() {
const { uuids } = this.state;
this.setState({ status: STATE.LOADING });
request.get('http://localhost:44405/commanders')
.then((data) => {
const [cmdr] = JSON.parse(data.text);
return Promise.all(
entries(uuids).map(
(entry) => {
const [uuid, n] = entry;
return new Promise((resolve, reject) => {
request.patch(`http://localhost:44405/${cmdr}/shopping-list`)
.field('uuid', uuid)
.field('size', n)
.end((err, res) => {
console.log('request goes out!');
if (err) {
reject(err);
} else {
resolve(res);
}
});
});
},
),
);
})
.then(() => this.setState({ status: STATE.DONE }))
.catch((err) => {
console.error(err);
this.setState({ status: STATE.ERROR });
});
}
/**
* Checks for browser compatibility of sending to ED Engineer.
* @returns {boolean} True if browser is compatible
*/
_browserIsCompatible() {
// !== Firefox 1.0+
// TODO: Double check if this really doesn't work in firefox
return typeof InstallTrigger === 'undefined';
}
/**
*
* @returns
*/
render() {
const { termtip, tooltip } = this.context;
const hide = tooltip.bind(null, null);
const { status } = this.state;
let msg = 'PHRASE_FIREFOX_EDENGINEER';
if (this._browserIsCompatible()) {
switch (status) {
case STATE.READY: msg = 'Send to EDEngineer'; break;
case STATE.LOADING: msg = 'Sending...'; break;
case STATE.ERROR: msg = 'Error sending to EDEngineer'; break;
case STATE.DONE: msg = 'Success! Clicking sends again.'; break;
}
}
return (<button
disabled={!this._browserIsCompatible()}
onClick={status !== STATE.LOADING && this._sendToEDEngineer}
onMouseOver={termtip.bind(null, msg)}
onMouseOut={hide}
>
{status === STATE.LOADING ?
<Loader className="lg" /> :
<MatIcon className="lg" />
}
</button>);
}
}

View File

@@ -0,0 +1,87 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import Slider from '../components/Slider';
import { moduleReduce } from 'ed-forge/lib/src/helper';
/**
* Engagement range slider
* Requires an onChange() function of the form onChange(range), providing the range in metres, which is triggered on range change
*/
export default class EngagementRange extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
engagementRange: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
this.state = {
maxRange: moduleReduce(
this.props.ship.getHardpoints(),
'maximumrange',
true,
// Don't use plain `Math.max` because callback will be passed four args
(a, v) => Math.max(a, v),
1000,
),
};
}
/**
* Update range
* @param {number} rangeLevel percentage level from 0 to 1
*/
_rangeChange(rangeLevel) {
const { maxRange } = this.state;
// We round the range to an integer value
const range = Math.round(rangeLevel * maxRange);
if (range !== this.props.engagementRange) {
const { onChange, ship } = this.props;
ship.setEngagementRange(range);
onChange(range);
}
}
/**
* Render range slider
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio } = this.context;
const { formats, translate } = language;
const { engagementRange } = this.props;
const { maxRange } = this.state;
return (
<span>
<h3>{translate('engagement range')}: {formats.int(engagementRange)}{translate('m')}</h3>
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
<tbody >
<tr>
<td>
<Slider
axis={true}
onChange={this._rangeChange.bind(this)}
axisUnit={translate('m')}
percent={engagementRange / maxRange}
max={maxRange}
scale={sizeRatio}
onResize={onWindowResize}
/>
</td>
</tr>
</tbody>
</table>
</span>
);
}
}

View File

@@ -0,0 +1,61 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import LineChart from '../components/LineChart';
import { getBoostMultiplier, getSpeedMultipliers } from 'ed-forge/lib/src/stats/SpeedProfile';
import { ShipProps } from 'ed-forge';
const { LADEN_MASS } = ShipProps;
/**
* Engine profile for a given ship
*/
export default class EngineProfile extends TranslatedComponent {
static propTypes = {
code: PropTypes.string.isRequired,
ship: PropTypes.object.isRequired,
cargo: PropTypes.number.isRequired,
fuel: PropTypes.number.isRequired,
pips: PropTypes.number.isRequired,
boost: PropTypes.bool.isRequired,
};
/**
* Render engine profile
* @return {React.Component} contents
*/
render() {
const { language } = this.context;
const { translate } = language;
const { code, ship, pips, boost } = this.props;
// Calculate bounds for our line chart
const minMass = ship.readProp('hullmass');
const maxMass = ship.getThrusters().get('enginemaximalmass');
const baseSpeed = ship.readProp('speed');
const baseBoost = getBoostMultiplier(ship);
const cb = (eng, boost, mass) => {
const mult = getSpeedMultipliers(ship, mass)[(boost ? 4 : eng) / 0.5];
return baseSpeed * (boost ? baseBoost : 1) * mult;
};
// This graph can have a precipitous fall-off so we use lots of points to make it look a little smoother
return (
<LineChart
xMin={minMass}
xMax={maxMass}
yMin={cb(0, false, maxMass)}
yMax={cb(4, true, minMass)}
// Add a mark at our current mass
xMark={Math.min(ship.get(LADEN_MASS), maxMass)}
xLabel={translate('mass')}
xUnit={translate('T')}
yLabel={translate('maximum speed')}
yUnit={translate('m/s')}
func={cb.bind(this, pips.Eng.base + pips.Eng.mc, boost)}
points={1000}
// Encode boost in code to re-render on state change
code={`${pips.Eng.base + pips.Eng.mc}:${Number(boost)}:${code}`}
aspect={0.7}
/>
);
}
}

View File

@@ -0,0 +1,52 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import LineChart from '../components/LineChart';
import { calculateJumpRange } from 'ed-forge/lib/src/stats/JumpRangeProfile';
import { ShipProps } from 'ed-forge';
const { LADEN_MASS } = ShipProps;
/**
* FSD profile for a given ship
*/
export default class FSDProfile extends TranslatedComponent {
static propTypes = {
code: PropTypes.string.isRequired,
ship: PropTypes.object.isRequired,
cargo: PropTypes.number.isRequired,
fuel: PropTypes.number.isRequired,
};
/**
* Render FSD profile
* @return {React.Component} contents
*/
render() {
const { language } = this.context;
const { translate } = language;
const { code, ship } = this.props;
const minMass = ship.readProp('hullmass');
const maxMass = ship.getThrusters().get('enginemaximalmass');
const mass = ship.get(LADEN_MASS);
const cb = (mass) => calculateJumpRange(ship.getFSD(), 0, mass, Infinity, true);
return (
<LineChart
xMin={minMass}
xMax={maxMass}
yMin={0}
yMax={cb(minMass)}
// Add a mark at our current mass
xMark={Math.min(mass, maxMass)}
xLabel={translate('mass')}
xUnit={translate('T')}
yLabel={translate('maximum range')}
yUnit={translate('LY')}
func={cb}
points={200}
code={code}
aspect={0.7}
/>
);
}
}

View File

@@ -0,0 +1,74 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import Slider from '../components/Slider';
import autoBind from 'auto-bind';
/**
* Fuel slider
* Requires an onChange() function of the form onChange(fuel), providing the fuel in tonnes, which is triggered on fuel level change
*/
export default class Fuel extends TranslatedComponent {
static propTypes = {
fuel: PropTypes.number.isRequired,
fuelCapacity: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
autoBind(this);
}
/**
* Update fuel level
* @param {number} fuelLevel percentage level from 0 to 1
*/
_fuelChange(fuelLevel) {
const { fuel, fuelCapacity } = this.props;
const newFuel = fuelLevel * fuelCapacity;
// Only send an update if the fuel has changed significantly
if (Math.round(fuel * 10) != Math.round(newFuel * 10)) {
this.props.onChange(Math.round(newFuel * 10) / 10);
}
}
/**
* Render fuel slider
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { fuel, fuelCapacity } = this.props;
return (
<span>
<h3>{translate('fuel carried')}: {formats.f1(fuel)}{units.T}</h3>
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
<tbody >
<tr>
<td>
<Slider
axis={true}
onChange={this._fuelChange}
axisUnit={translate('T')}
percent={fuel / fuelCapacity}
max={fuelCapacity}
scale={sizeRatio}
onResize={onWindowResize}
/>
</td>
</tr>
</tbody>
</table>
</span>
);
}
}

View File

@@ -1,60 +0,0 @@
import React from 'react';
import Slot from './Slot';
/**
* Hardpoint / Utility Slot
*/
export default class HardpointSlot extends Slot {
/**
* Get the CSS class name for the slot.
* @return {string} CSS Class name
*/
_getClassNames() {
return this.props.maxClass > 0 ? 'hardpoint' : null;
}
/**
* Get the label for the slot
* @param {Function} translate Translate function
* @return {string} Label
*/
_getMaxClassLabel(translate) {
return translate(['U','S','M','L','H'][this.props.maxClass]);
}
/**
* Generate the slot contents
* @param {Object} m Mounted Module
* @param {Function} translate Translate function
* @param {Object} formats Localized Formats map
* @param {Object} u Localized Units Map
* @return {React.Component} Slot contents
*/
_getSlotDetails(m, translate, formats, u) {
if (m) {
let classRating = `${m.class}${m.rating}${m.mount ? '/' + m.mount : ''}${m.missile ? m.missile : ''}`;
let { drag, drop } = this.props;
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}>
<div className={'l'}>{classRating} {translate(m.name || m.grp)}</div>
<div className={'r'}>{m.mass}{u.T}</div>
</div>
<div className={'cb'}>
{ m.damage ? <div className={'l'}>{translate('damage')}: {m.damage} { m.ssdam ? <span>({formats.int(m.ssdam)} {u.MJ})</span> : null }</div> : null }
{ m.dps ? <div className={'l'}>{translate('DPS')}: {m.dps} { m.mjdps ? <span>({formats.int(m.mjdps)} {u.MJ})</span> : null }</div> : null }
{ m.thermload ? <div className={'l'}>{translate('T-Load')}: {m.thermload}</div> : null }
{ m.type ? <div className={'l'}>{translate('type')}: {m.type}</div> : null }
{ m.rof ? <div className={'l'}>{translate('ROF')}: {m.rof}{u.ps}</div> : null }
{ m.armourpen ? <div className={'l'}>{translate('pen')}: {m.armourpen}</div> : null }
{ m.shieldmul ? <div className={'l'}>+{formats.rPct(m.shieldmul)}</div> : null }
{ m.range ? <div className={'l'}>{m.range} <u>km</u></div> : null }
{ m.ammo >= 0 ? <div className={'l'}>{translate('ammo')}: {formats.int(m.clip)}+{formats.int(m.ammo)}</div> : null }
</div>
</div>;
} else {
return <div className={'empty'}>{translate('empty')}</div>;
}
}
}

View File

@@ -0,0 +1,154 @@
import React from 'react';
import SlotSection from './SlotSection';
import Slot from './Slot';
import { MountFixed, MountGimballed, MountTurret } from '../components/SvgIcons';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import autoBind from 'auto-bind';
const SIZE_ORDER = ['huge', 'large', 'medium', 'small'];
/**
* Hardpoint slot section
*/
export default class HardpointSlotSection extends SlotSection {
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props, 'hardpoints');
autoBind(this);
}
/**
* Empty all slots
*/
_empty() {
this.props.ship.getHardpoints(undefined, true).forEach((slot) => slot.reset());
this._close();
}
/**
* Fill slots with specified module
* @param {string} type Type of item
* @param {string} rating Mount Type - (fixed, gimbal, turret)
* @param {SyntheticEvent} event Event
*/
_fill(type, rating, event) {
const fillAll = event.getModifierState('Alt');
this.props.ship.getHardpoints(undefined, true).forEach((slot) => {
if (slot.isEmpty() || fillAll) {
const slotSize = slot.getSize();
const fittingSizes = SIZE_ORDER.slice(SIZE_ORDER.findIndex((e) => e === slotSize));
for (const size of fittingSizes) {
try {
slot.setItem(type, size, rating);
} catch (err) {
// Try next item if this doesn't fit/exist
continue;
}
// If still here, we were able to apply the module
break;
}
}
});
this._close();
}
/**
* Generate the slot React Components
* @return {Array} Array of Slots
*/
_getSlots() {
let { ship, currentMenu, propsToShow, onPropToggle } = this.props;
let { originSlot, targetSlot } = this.state;
let slots = [];
for (let h of ship.getHardpoints(undefined, true)) {
slots.push(<Slot
key={h.object.Slot}
currentMenu={currentMenu}
drag={this._drag.bind(this, h)}
dragOver={this._dragOverSlot.bind(this, h)}
drop={this._drop}
dropClass={this._dropClass(h, originSlot, targetSlot)}
m={h}
enabled={h.enabled ? true : false}
propsToShow={propsToShow}
onPropToggle={onPropToggle}
/>);
}
return slots;
}
/**
* Generate the section drop-down menu
* @param {Function} translate Translate function
* @return {React.Component} Section menu
*/
_getSectionMenu() {
const { translate } = this.context.language;
let _fill = this._fill;
return <div className='select hardpoint' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' tabIndex="0" onClick={this._empty}>{translate('empty all')}</li>
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul>
<div className='select-group cap'>{translate('pulselaser')}</div>
<ul>
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'pulselaser', 'fixed')}><MountFixed className='lg'/></li>
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'pulselaser', 'gimbal')}><MountGimballed className='lg'/></li>
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'pulselaser', 'turret')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('burstlaser')}</div>
<ul>
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'burstlaser', 'fixed')}><MountFixed className='lg'/></li>
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'burstlaser', 'gimbal')}><MountGimballed className='lg'/></li>
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'burstlaser', 'turret')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('beamlaser')}</div>
<ul>
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'beamlaser', 'fixed')}><MountFixed className='lg'/></li>
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'beamlaser', 'gimbal')}><MountGimballed className='lg'/></li>
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'beamlaser', 'turret')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('multicannon')}</div>
<ul>
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'multicannon', 'fixed')}><MountFixed className='lg'/></li>
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'multicannon', 'gimbal')}><MountGimballed className='lg'/></li>
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'multicannon', 'turret')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('cannon')}</div>
<ul>
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'cannon', 'fixed')}><MountFixed className='lg'/></li>
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'cannon', 'gimbal')}><MountGimballed className='lg'/></li>
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'cannon', 'turret')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('fragcannon')}</div>
<ul>
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'fragcannon', 'fixed')}><MountFixed className='lg'/></li>
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'fragcannon', 'gimbal')}><MountGimballed className='lg'/></li>
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'fragcannon', 'turret')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('plasmaacc')}</div>
<ul>
<li className='lc' tabIndex="0" onClick={_fill.bind(this, 'plasmaacc', 'fixed')}>{translate('pa')}</li>
</ul>
<div className='select-group cap'>{translate('railgun')}</div>
<ul>
<li className='lc' tabIndex="0" onClick={_fill.bind(this, 'railgun', 'fixed')}>{translate('rg')}</li>
</ul>
<div className='select-group cap'>{translate('minelauncher')}</div>
<ul>
<li className='lc' tabIndex="0" onClick={_fill.bind(this, 'minelauncher', 'fixed')}>{translate('nl')}</li>
</ul>
<div className='select-group cap'>{translate('flaklauncher')}</div>
<ul>
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'flaklauncher', 'fixed')}><MountFixed className='lg'/></li>
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'flaklauncher', 'turret')}><MountTurret className='lg'/></li>
</ul>
</div>;
}
}

View File

@@ -1,132 +0,0 @@
import React from 'react';
import SlotSection from './SlotSection';
import HardpointSlot from './HardpointSlot';
import cn from 'classnames';
import { MountFixed, MountGimballed, MountTurret } from '../components/SvgIcons';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
/**
* Hardpoint slot section
*/
export default class HardpointsSlotSection extends SlotSection {
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props, context, 'hardpoints', 'hardpoints');
this._empty = this._empty.bind(this);
}
/**
* Empty all slots
*/
_empty() {
this.props.ship.emptyWeapons();
this.props.onChange();
this._close();
}
/**
* Fill slots with specified module
* @param {string} group Group name
* @param {string} mount Mount Type - F, G, T
* @param {SyntheticEvent} event Event
*/
_fill(group, mount, event) {
this.props.ship.useWeapon(group, mount, null, event.getModifierState('Alt'));
this.props.onChange();
this._close();
}
/**
* Empty all on section header right click
*/
_contextMenu() {
this._empty();
}
/**
* Generate the slot React Components
* @return {Array} Array of Slots
*/
_getSlots() {
let { ship, currentMenu } = this.props;
let { originSlot, targetSlot } = this.state;
let slots = [];
let hardpoints = ship.hardpoints;
let availableModules = ship.getAvailableModules();
for (let i = 0, l = hardpoints.length; i < l; i++) {
let h = hardpoints[i];
if (h.maxClass) {
slots.push(<HardpointSlot
key={i}
maxClass={h.maxClass}
availableModules={() => availableModules.getHps(h.maxClass)}
onOpen={this._openMenu.bind(this, h)}
onSelect={this._selectModule.bind(this, h)}
selected={currentMenu == h}
drag={this._drag.bind(this, h)}
dragOver={this._dragOverSlot.bind(this, h)}
drop={this._drop}
dropClass={this._dropClass(h, originSlot, targetSlot)}
ship={ship}
m={h.m}
/>);
}
}
return slots;
}
/**
* Generate the section drop-down menu
* @param {Function} translate Translate function
* @return {React.Component} Section menu
*/
_getSectionMenu(translate) {
let _fill = this._fill;
return <div className='select hardpoint' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul>
<div className='select-group cap'>{translate('pl')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'pl', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'pl', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'pl', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('ul')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'ul', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'ul', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'ul', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('bl')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'bl', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'bl', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'bl', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('mc')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'mc', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'mc', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'mc', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('c')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'c', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'c', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'c', 'T')}><MountTurret className='lg'/></li>
</ul>
</div>;
}
}

View File

@@ -5,15 +5,18 @@ import { Insurance } from '../shipyard/Constants';
import Link from './Link'; import Link from './Link';
import ActiveLink from './ActiveLink'; import ActiveLink from './ActiveLink';
import cn from 'classnames'; import cn from 'classnames';
import { Cogs, CoriolisLogo, Hammer, Rocket, StatsBars } from './SvgIcons'; import { Cogs, CoriolisLogo, Hammer, Help, Rocket, StatsBars } from './SvgIcons';
import { Ships } from 'coriolis-data/dist';
import Persist from '../stores/Persist'; import Persist from '../stores/Persist';
import { toDetailedExport } from '../shipyard/Serializer';
import ModalDeleteAll from './ModalDeleteAll'; import ModalDeleteAll from './ModalDeleteAll';
import ModalExport from './ModalExport'; import ModalExport from './ModalExport';
import ModalHelp from './ModalHelp';
import ModalImport from './ModalImport'; import ModalImport from './ModalImport';
import Slider from './Slider'; import Slider from './Slider';
import Announcement from './Announcement';
import { outfitURL } from '../utils/UrlGenerators'; import { outfitURL } from '../utils/UrlGenerators';
import autoBind from 'auto-bind';
import { Factory, Ship } from 'ed-forge';
import { chain, entries } from 'lodash';
const SIZE_MIN = 0.65; const SIZE_MIN = 0.65;
const SIZE_RANGE = 0.55; const SIZE_RANGE = 0.55;
@@ -27,7 +30,7 @@ function normalizePercent(val) {
if (val === '' || isNaN(val)) { if (val === '' || isNaN(val)) {
return 0; return 0;
} }
val = Math.round(val * 100) / 100; val = Math.round(val * 1000) / 1000;
return val >= 100 ? 100 : val; return val >= 100 ? 100 : val;
} }
@@ -52,7 +55,6 @@ function selectAll(e) {
* Coriolis App Header section / menus * Coriolis App Header section / menus
*/ */
export default class Header extends TranslatedComponent { export default class Header extends TranslatedComponent {
/** /**
* Constructor * Constructor
* @param {Object} props React Component properties * @param {Object} props React Component properties
@@ -60,19 +62,13 @@ export default class Header extends TranslatedComponent {
*/ */
constructor(props, context) { constructor(props, context) {
super(props); super(props);
this.shipOrder = Object.keys(Ships).sort(); autoBind(this);
this.ships = Factory.getAllShipTypes().sort();
this._setLanguage = this._setLanguage.bind(this);
this._setInsurance = this._setInsurance.bind(this);
this._setShipDiscount = this._setShipDiscount.bind(this);
this._changeShipDiscount = this._changeShipDiscount.bind(this);
this._kpShipDiscount = this._kpShipDiscount.bind(this);
this._setModuleDiscount = this._setModuleDiscount.bind(this);
this._changeModuleDiscount = this._changeModuleDiscount.bind(this);
this._kpModuleDiscount = this._kpModuleDiscount.bind(this);
this._openShips = this._openMenu.bind(this, 's'); this._openShips = this._openMenu.bind(this, 's');
this._openBuilds = this._openMenu.bind(this, 'b'); this._openBuilds = this._openMenu.bind(this, 'b');
this._openComp = this._openMenu.bind(this, 'comp'); this._openComp = this._openMenu.bind(this, 'comp');
this._openAnnounce = this._openMenu.bind(this, 'announce');
this._openSettings = this._openMenu.bind(this, 'settings'); this._openSettings = this._openMenu.bind(this, 'settings');
this.languageOptions = []; this.languageOptions = [];
this.insuranceOptions = []; this.insuranceOptions = [];
@@ -212,20 +208,6 @@ export default class Header extends TranslatedComponent {
this.context.showModal(<ModalDeleteAll />); this.context.showModal(<ModalDeleteAll />);
}; };
/**
* Show export modal with backup data
* @param {SyntheticEvent} e Event
*/
_showBackup(e) {
let translate = this.context.language.translate;
e.preventDefault();
this.context.showModal(<ModalExport
title={translate('backup')}
description={translate('PHRASE_BACKUP_DESC')}
data={Persist.getAll()}
/>);
};
/** /**
* Show export modal with detailed export * Show export modal with detailed export
* @param {SyntheticEvent} e Event * @param {SyntheticEvent} e Event
@@ -234,13 +216,36 @@ export default class Header extends TranslatedComponent {
let translate = this.context.language.translate; let translate = this.context.language.translate;
e.preventDefault(); e.preventDefault();
const builds = chain(Persist.getBuilds())
.values()
.map((builds) => Object.values(builds))
.flatMap()
.map((code) => new Ship(code))
.value();
this.context.showModal(<ModalExport this.context.showModal(<ModalExport
title={translate('detailed export')} title={translate('detailed export')}
description={translate('PHRASE_EXPORT_DESC')} description={translate('PHRASE_EXPORT_DESC')}
data={toDetailedExport(Persist.getBuilds())} data={JSON.stringify(builds.map((build) => {
return {
header: { appName: 'Inara', 'appVersion': '1.0' },
data: build.toJSON(),
};
}))}
/>); />);
} }
/**
* Show help modal
* @param {SyntheticEvent} e Event
*/
_showHelp(e) {
let translate = this.context.language.translate;
e.preventDefault();
this.context.showModal(<ModalHelp title={translate('help')} />);
}
/** /**
* Show import modal * Show import modal
* @param {SyntheticEvent} e Event * @param {SyntheticEvent} e Event
@@ -284,15 +289,10 @@ export default class Header extends TranslatedComponent {
* @return {React.Component} Menu * @return {React.Component} Menu
*/ */
_getShipsMenu() { _getShipsMenu() {
let shipList = []; const { translate } = this.context.language;
for (let s in Ships) {
shipList.push(<ActiveLink key={s} href={outfitURL(s)} className='block'>{Ships[s].properties.name}</ActiveLink>);
}
return ( return (
<div className='menu-list dbl no-wrap' onClick={ (e) => e.stopPropagation() }> <div className='menu-list dbl no-wrap' onClick={ (e) => e.stopPropagation() }>
{shipList} {this.ships.map((s) => <ActiveLink key={s} href={outfitURL(s)} className='block'>{translate(s)}</ActiveLink>)}
</div> </div>
); );
} }
@@ -302,9 +302,10 @@ export default class Header extends TranslatedComponent {
* @return {React.Component} Menu * @return {React.Component} Menu
*/ */
_getBuildsMenu() { _getBuildsMenu() {
const { translate } = this.context.language;
let builds = Persist.getBuilds(); let builds = Persist.getBuilds();
let buildList = []; let buildList = [];
for (let shipId of this.shipOrder) { for (let shipId of this.ships) {
if (builds[shipId]) { if (builds[shipId]) {
let shipBuilds = []; let shipBuilds = [];
let buildNameOrder = Object.keys(builds[shipId]).sort(); let buildNameOrder = Object.keys(builds[shipId]).sort();
@@ -312,7 +313,7 @@ export default class Header extends TranslatedComponent {
let href = outfitURL(shipId, builds[shipId][buildName], buildName); let href = outfitURL(shipId, builds[shipId][buildName], buildName);
shipBuilds.push(<li key={shipId + '-' + buildName} ><ActiveLink href={href} className='block'>{buildName}</ActiveLink></li>); shipBuilds.push(<li key={shipId + '-' + buildName} ><ActiveLink href={href} className='block'>{buildName}</ActiveLink></li>);
} }
buildList.push(<ul key={shipId}>{Ships[shipId].properties.name}{shipBuilds}</ul>); buildList.push(<ul key={shipId}>{translate(shipId)}{shipBuilds}</ul>);
} }
} }
@@ -336,7 +337,7 @@ export default class Header extends TranslatedComponent {
let comps = Object.keys(Persist.getComparisons()).sort(); let comps = Object.keys(Persist.getComparisons()).sort();
for (let name of comps) { for (let name of comps) {
comparisons.push(<ActiveLink key={name} href={'/compare/' + name} className='block name'>{name}</ActiveLink>); comparisons.push(<ActiveLink key={name} href={'/compare/' + encodeURIComponent(name)} className='block name'>{name}</ActiveLink>);
} }
} else { } else {
comparisons = <span className='cap'>{translate('none created')}</span>; comparisons = <span className='cap'>{translate('none created')}</span>;
@@ -352,6 +353,32 @@ export default class Header extends TranslatedComponent {
); );
} }
/**
* Generate the announcement menu
* @return {React.Component} Menu
*/
_getAnnouncementsMenu() {
let announcements;
let translate = this.context.language.translate;
if (this.props.announcements) {
announcements = [];
for (let announce of this.props.announcements) {
if (announce.expiry < Date.now()) {
continue;
}
announcements.push(<Announcement text={announce.text} />);
announcements.push(<hr/>);
}
}
return (
<div className='menu-list' onClick={ (e) => e.stopPropagation() } style={{ whiteSpace: 'nowrap' }}>
{announcements}
<hr />
</div>
);
}
/** /**
* Generate the settings menu * Generate the settings menu
* @return {React.Component} Menu * @return {React.Component} Menu
@@ -403,10 +430,9 @@ export default class Header extends TranslatedComponent {
<hr /> <hr />
<ul style={{ width: '100%' }}> <ul style={{ width: '100%' }}>
{translate('builds')} & {translate('comparisons')} {translate('builds')} & {translate('comparisons')}
<li><Link href="#" className='block' onClick={this._showBackup.bind(this)}>{translate('backup')}</Link></li> <li><Link href="#" className='block' onClick={this._showDetailedExport}>{translate('detailed export')}</Link></li>
<li><Link href="#" className='block' onClick={this._showDetailedExport.bind(this)}>{translate('detailed export')}</Link></li> <li><Link href="#" className='block' onClick={this._showImport}>{translate('import')}</Link></li>
<li><Link href="#" className='block' onClick={this._showImport.bind(this)}>{translate('import')}</Link></li> <li><Link href="#" className='block' onClick={this._showDeleteAll}>{translate('delete all')}</Link></li>
<li><Link href="#" className='block' onClick={this._showDeleteAll.bind(this)}>{translate('delete all')}</Link></li>
</ul> </ul>
<hr /> <hr />
<table style={{ width: 300, backgroundColor: 'transparent' }}> <table style={{ width: 300, backgroundColor: 'transparent' }}>
@@ -417,7 +443,7 @@ export default class Header extends TranslatedComponent {
<td style={{ width: 20 }}><span style={{ fontSize: 30 }}>A</span></td> <td style={{ width: 20 }}><span style={{ fontSize: 30 }}>A</span></td>
</tr> </tr>
<tr> <tr>
<td colSpan='3' style={{ textAlign: 'center', cursor: 'pointer' }} className='primary-disabled cap' onClick={this._resetTextSize.bind(this)}>{translate('reset')}</td> <td colSpan='3' style={{ textAlign: 'center', cursor: 'pointer' }} className='primary-disabled cap' onClick={this._resetTextSize.bind(this)}>{translate('reset')}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -468,6 +494,15 @@ export default class Header extends TranslatedComponent {
} }
} }
async update() {
const reg = await navigator.serviceWorker.getRegistration();
if (!reg || !reg.waiting) {
return window.location.reload();
}
reg.waiting.postMessage('skipWaiting');
window.location.reload();
}
/** /**
* Render the header * Render the header
* @return {React.Component} Header * @return {React.Component} Header
@@ -478,7 +513,10 @@ export default class Header extends TranslatedComponent {
let hasBuilds = Persist.hasBuilds(); let hasBuilds = Persist.hasBuilds();
return ( return (
<header> <header>
{this.props.appCacheUpdate && <div id="app-update" onClick={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>} {this.props.appCacheUpdate && <div id="app-update" onClick={this.update}>{translate('PHRASE_UPDATE_RDY')}</div>}
{this.props.appCacheUpdate ? <a className={'view-changes'} href={'https://github.com/EDCD/coriolis/compare/edcd:develop@{' + window.CORIOLIS_DATE + '}...edcd:develop'} target="_blank">
{'View Release Changes'}
</a> : null}
<Link className='l' href='/' style={{ marginRight: '1em' }} title='Home'><CoriolisLogo className='icon xl' /></Link> <Link className='l' href='/' style={{ marginRight: '1em' }} title='Home'><CoriolisLogo className='icon xl' /></Link>
<div className='l menu'> <div className='l menu'>
@@ -495,12 +533,23 @@ export default class Header extends TranslatedComponent {
{openedMenu == 'b' ? this._getBuildsMenu() : null} {openedMenu == 'b' ? this._getBuildsMenu() : null}
</div> </div>
<div className='l menu'> {/* TODO: Enable */}
<div className={cn('menu-header', { selected: openedMenu == 'comp', disabled: !hasBuilds })} onClick={hasBuilds && this._openComp}> {/* <div className='l menu'>
<StatsBars className={cn('warning', { 'warning-disabled': !hasBuilds })} /><span className='menu-item-label'>{translate('compare')}</span> <div className={cn('menu-header', { selected: openedMenu == 'announce', disabled: this.props.announcements.length === 0 })} onClick={this.props.announcements.length !== 0 && this._openAnnounce}>
<span className='menu-item-label'>{translate('announcements')}</span>
</div> </div>
{openedMenu == 'comp' ? this._getComparisonsMenu() : null} {openedMenu == 'announce' ? this._getAnnouncementsMenu() : null}
</div> </div> */}
{window.location.origin.search('.edcd.io') >= 0 ?
<div className='l menu'>
<a href="https://youtu.be/4SvnLcefhtI" target="_blank">
<div className={cn('menu-header')}>
<Rocket className='warning'/><span className='menu-item-label'>{translate('please migrate to coriolis.io')}</span>
</div>
</a>
</div> : null
}
<div className='r menu'> <div className='r menu'>
<div className={cn('menu-header', { selected: openedMenu == 'settings' })} onClick={this._openSettings}> <div className={cn('menu-header', { selected: openedMenu == 'settings' })} onClick={this._openSettings}>
@@ -508,8 +557,13 @@ export default class Header extends TranslatedComponent {
</div> </div>
{openedMenu == 'settings' ? this._getSettingsMenu() : null} {openedMenu == 'settings' ? this._getSettingsMenu() : null}
</div> </div>
<div className='r menu'>
<div className={cn('menu-header')} onClick={this._showHelp}>
<Help className='xl warning'/>
</div>
</div>
</header> </header>
); );
} }
}
}

View File

@@ -1,51 +0,0 @@
import React from 'react';
import Slot from './Slot';
import { Infinite } from './SvgIcons';
/**
* Internal Slot
*/
export default class InternalSlot extends Slot {
/**
* Generate the slot contents
* @param {Object} m Mounted Module
* @param {Function} translate Translate function
* @param {Object} formats Localized Formats map
* @param {Object} u Localized Units Map
* @return {React.Component} Slot contents
*/
_getSlotDetails(m, translate, formats, u) {
if (m) {
let classRating = m.class + m.rating;
let { drag, drop } = this.props;
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}>
<div className={'l'}>{classRating} {translate(m.name || m.grp)}</div>
<div className={'r'}>{m.mass || m.cargo || m.fuel || 0}{u.T}</div>
</div>
<div className={'cb'}>
{ m.optmass ? <div className={'l'}>{translate('optimal mass')}: {m.optmass}{u.T}</div> : null }
{ m.maxmass ? <div className={'l'}>{translate('max mass')}: {m.maxmass}{u.T}</div> : null }
{ m.bins ? <div className={'l'}>{m.bins} <u>{translate('bins')}</u></div> : null }
{ m.bays ? <div className={'l'}>{translate('bays')}: {m.bays}</div> : null }
{ m.rate ? <div className={'l'}>{translate('rate')}: {m.rate}{u.kgs}&nbsp;&nbsp;&nbsp;{translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}</div> : null }
{ m.ammo ? <div className={'l'}>{translate('ammo')}: {formats.gen(m.ammo)}</div> : null }
{ m.cells ? <div className={'l'}>{translate('cells')}: {m.cells}</div> : null }
{ m.recharge ? <div className={'l'}>{translate('recharge')}: {m.recharge} <u>MJ</u>&nbsp;&nbsp;&nbsp;{translate('total')}: {m.cells * m.recharge}{u.MJ}</div> : null }
{ m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null }
{ m.range ? <div className={'l'}>{translate('range')} {m.range}{u.km}</div> : null }
{ m.time ? <div className={'l'}>{translate('time')}: {formats.time(m.time)}</div> : null }
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
{ m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
{ m.rangeLS === null ? <div className={'l'}><Infinite/>{u.Ls}</div> : null }
{ m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</div> : null }
{ m.armouradd ? <div className={'l'}>+{m.armouradd} <u className='cap'>{translate('armour')}</u></div> : null }
</div>
</div>;
} else {
return <div className={'empty'}>{translate('empty')}</div>;
}
}
}

View File

@@ -1,35 +1,46 @@
import React from 'react'; import React from 'react';
import cn from 'classnames';
import SlotSection from './SlotSection'; import SlotSection from './SlotSection';
import InternalSlot from './InternalSlot'; import Slot from './Slot';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import { stopCtxPropagation } from '../utils/UtilityFunctions'; import { stopCtxPropagation } from '../utils/UtilityFunctions';
import autoBind from 'auto-bind';
import { TYPES } from 'ed-forge/lib/src/data/slots';
/**
* Sets all empty slots of a ship to a item of the given size.
* @param {Ship} ship Ship to set items for
* @param {boolean} fillAll True to also fill occupied
* @param {string} type Item type
* @param {string} rating Item rating
*/
function setAllEmpty(ship, fillAll, type, rating = '') {
ship.getModules(TYPES.ANY_INTERNAL, undefined, true).forEach((slot) => {
if (slot.isEmpty() || fillAll) {
try {
// Maybe the item does not exist. Simply catch this error.
slot.setItem(type, slot.getSize(), rating);
} catch (e) {}
}
});
}
/** /**
* Internal slot section * Internal slot section
*/ */
export default class InternalSlotSection extends SlotSection { export default class InternalSlotSection extends SlotSection {
/** /**
* Constructor * Constructor
* @param {Object} props React Component properties * @param {Object} props React Component properties
* @param {Object} context React Component context
*/ */
constructor(props, context) { constructor(props) {
super(props, context, 'internal', 'internal compartments'); super(props, 'optional internal');
autoBind(this);
this._empty = this._empty.bind(this);
this._fillWithCargo = this._fillWithCargo.bind(this);
this._fillWithCells = this._fillWithCells.bind(this);
this._fillWithArmor = this._fillWithArmor.bind(this);
} }
/** /**
* Empty all slots * Empty all slots
*/ */
_empty() { _empty() {
this.props.ship.emptyInternal(); this.props.ship.getModules(TYPES.ANY_INTERNAL).forEach((slot) => slot.reset());
this.props.onChange();
this._close(); this._close();
} }
@@ -38,14 +49,58 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithCargo(event) { _fillWithCargo(event) {
let clobber = event.getModifierState('Alt'); const fillAll = event.getModifierState('Alt');
let ship = this.props.ship; setAllEmpty(this.props.ship, fillAll, 'cargorack');
ship.internal.forEach((slot) => { this._close();
if (clobber || !slot.m) { }
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
} /**
}); * Fill all slots with fuel tanks
this.props.onChange(); * @param {SyntheticEvent} event Event
*/
_fillWithFuelTanks(event) {
const fillAll = event.getModifierState('Alt');
setAllEmpty(this.props.ship, fillAll, 'fueltank', '3');
this._close();
}
/**
* Fill all slots with luxury passenger cabins
* @param {SyntheticEvent} event Event
*/
_fillWithLuxuryCabins(event) {
const fillAll = event.getModifierState('Alt');
setAllEmpty(this.props.ship, fillAll, 'passengercabins', '4');
this._close();
}
/**
* Fill all slots with first class passenger cabins
* @param {SyntheticEvent} event Event
*/
_fillWithFirstClassCabins(event) {
const fillAll = event.getModifierState('Alt');
setAllEmpty(this.props.ship, fillAll, 'passengercabins', '3');
this._close();
}
/**
* Fill all slots with business class passenger cabins
* @param {SyntheticEvent} event Event
*/
_fillWithBusinessClassCabins(event) {
const fillAll = event.getModifierState('Alt');
setAllEmpty(this.props.ship, fillAll, 'passengercabins', '2');
this._close();
}
/**
* Fill all slots with economy class passenger cabins
* @param {SyntheticEvent} event Event
*/
_fillWithEconomyClassCabins(event) {
const fillAll = event.getModifierState('Alt');
setAllEmpty(this.props.ship, fillAll, 'passengercabins', '1');
this._close(); this._close();
} }
@@ -54,17 +109,8 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithCells(event) { _fillWithCells(event) {
let clobber = event.getModifierState('Alt'); const fillAll = event.getModifierState('Alt');
let ship = this.props.ship; setAllEmpty(this.props.ship, fillAll, 'scb', '5');
let chargeCap = 0; // Capacity of single activation
ship.internal.forEach(function(slot) {
if ((!slot.m || (clobber && !ModuleUtils.isShieldGenerator(slot.m.grp))) && (!slot.eligible || slot.eligible.scb)) { // Check eligibility due to Orca special case
ship.use(slot, ModuleUtils.findInternal('scb', slot.maxClass, 'A'));
ship.setSlotEnabled(slot, chargeCap <= ship.shieldStrength); // Don't waste cell capacity on overcharge
chargeCap += slot.m.recharge;
}
});
this.props.onChange();
this._close(); this._close();
} }
@@ -73,22 +119,19 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithArmor(event) { _fillWithArmor(event) {
let clobber = event.getModifierState('Alt'); const fillAll = event.getModifierState('Alt');
let ship = this.props.ship; setAllEmpty(this.props.ship, fillAll, 'hrp', '2');
ship.internal.forEach((slot) => {
if (clobber || !slot.m) {
ship.use(slot, ModuleUtils.findInternal('hr', Math.min(slot.maxClass, 5), 'D')); // Hull reinforcements top out at 5D
}
});
this.props.onChange();
this._close(); this._close();
} }
/** /**
* Empty all on section header right click * Fill all slots with Module Reinforcement Packages
* @param {SyntheticEvent} event Event
*/ */
_contextMenu() { _fillWithModuleReinforcementPackages(event) {
this._empty(); const fillAll = event.getModifierState('Alt');
setAllEmpty(this.props.ship, fillAll, 'mrp', '2');
this._close();
} }
/** /**
@@ -97,29 +140,20 @@ export default class InternalSlotSection extends SlotSection {
*/ */
_getSlots() { _getSlots() {
let slots = []; let slots = [];
let { currentMenu, ship } = this.props; let { currentMenu, ship, propsToShow, onPropToggle } = this.props;
let { originSlot, targetSlot } = this.state; let { originSlot, targetSlot } = this.state;
let { internal, fuelCapacity, ladenMass } = ship;
let availableModules = ship.getAvailableModules();
for (let i = 0, l = internal.length; i < l; i++) { for (const m of ship.getInternals(undefined, true)) {
let s = internal[i]; slots.push(<Slot
key={m.object.Slot}
slots.push(<InternalSlot currentMenu={currentMenu}
key={i} m={m}
maxClass={s.maxClass} drag={this._drag.bind(this, m)}
availableModules={() => availableModules.getInts(s.maxClass, s.eligible)} dragOver={this._dragOverSlot.bind(this, m)}
onOpen={this._openMenu.bind(this,s)}
onSelect={this._selectModule.bind(this, s)}
selected={currentMenu == s}
enabled={s.enabled}
m={s.m}
drag={this._drag.bind(this, s)}
dragOver={this._dragOverSlot.bind(this, s)}
drop={this._drop} drop={this._drop}
dropClass={this._dropClass(s, originSlot, targetSlot)} dropClass={this._dropClass(m, originSlot, targetSlot)}
fuel={fuelCapacity} propsToShow={propsToShow}
ship={ship} onPropToggle={onPropToggle}
/>); />);
} }
@@ -129,18 +163,26 @@ export default class InternalSlotSection extends SlotSection {
/** /**
* Generate the section drop-down menu * Generate the section drop-down menu
* @param {Function} translate Translate function * @param {Function} translate Translate function
* @param {Function} ship The ship
* @return {React.Component} Section menu * @return {React.Component} Section menu
*/ */
_getSectionMenu(translate) { _getSectionMenu() {
const { ship } = this.props;
const { translate } = this.context.language;
return <div className='select' onClick={e => e.stopPropagation()} onContextMenu={stopCtxPropagation}> return <div className='select' onClick={e => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul> <ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li> <li className='lc' tabIndex='0' onClick={this._empty}>{translate('empty all')}</li>
<li className='lc' onClick={this._fillWithCargo}>{translate('cargo')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithCargo}>{translate('cargo')}</li>
<li className='lc' onClick={this._fillWithCells}>{translate('scb')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithCells}>{translate('scb')}</li>
<li className='lc' onClick={this._fillWithArmor}>{translate('hr')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithArmor}>{translate('hr')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithModuleReinforcementPackages}>{translate('mrp')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithFuelTanks}>{translate('ft')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithEconomyClassCabins}>{translate('pce')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithBusinessClassCabins}>{translate('pci')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithFirstClassCabins} onKeyDown={ship.luxuryCabins ? '' : this._keyDown}>{translate('pcm')}</li>
{ ship.readMeta('luxuryCabins') ? <li className='lc' tabIndex='0' onClick={this._fillWithLuxuryCabins}>{translate('pcq')}</li> : ''}
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li> <li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul> </ul>
</div>; </div>;
} }
} }

View File

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

View File

@@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import Router from '../Router'; import Router from '../Router';
import { shallowEqual } from '../utils/UtilityFunctions'; import { shallowEqual } from '../utils/UtilityFunctions';
@@ -6,11 +7,10 @@ import { shallowEqual } from '../utils/UtilityFunctions';
* Link wrapper component * Link wrapper component
*/ */
export default class Link extends React.Component { export default class Link extends React.Component {
static propTypes = { static propTypes = {
children: React.PropTypes.any, children: PropTypes.any,
href: React.PropTypes.string.isRequired, href: PropTypes.string.isRequired,
onClick: React.PropTypes.func onClick: PropTypes.func
}; };
/** /**
@@ -36,7 +36,7 @@ export default class Link extends React.Component {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
handler(event) { handler(event) {
if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey || event.button > 1) { if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey || event.button > 0) {
return; return;
} }
event.preventDefault(); event.preventDefault();
@@ -55,5 +55,4 @@ export default class Link extends React.Component {
render() { render() {
return <a {...this.props} onClick={this.handler}>{this.props.children}</a>; return <a {...this.props} onClick={this.handler}>{this.props.children}</a>;
} }
}
}

View File

@@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist'; import { Ships } from 'coriolis-data/dist';
import Persist from '../stores/Persist'; import Persist from '../stores/Persist';
@@ -20,10 +21,9 @@ function buildComparator(a, b) {
* Compare builds modal * Compare builds modal
*/ */
export default class ModalCompare extends TranslatedComponent { export default class ModalCompare extends TranslatedComponent {
static propTypes = { static propTypes = {
onSelect: React.PropTypes.func.isRequired, onSelect: PropTypes.func.isRequired,
builds: React.PropTypes.array builds: PropTypes.array
}; };
static defaultProps = { static defaultProps = {
@@ -104,8 +104,8 @@ export default class ModalCompare extends TranslatedComponent {
let selectedBuilds = usedBuilds.map((build, i) => let selectedBuilds = usedBuilds.map((build, i) =>
<tr key={i} onClick={this._removeBuild.bind(this, i)}> <tr key={i} onClick={this._removeBuild.bind(this, i)}>
<td className='tl'>{build.name}</td>< <td className='tl'>{build.name}</td>
td className='tl'>{build.buildName}</td> <td className='tl'>{build.buildName}</td>
</tr> </tr>
); );

View File

@@ -6,7 +6,6 @@ import Persist from '../stores/Persist';
* Delete All saved data modal * Delete All saved data modal
*/ */
export default class ModalDeleteAll extends TranslatedComponent { export default class ModalDeleteAll extends TranslatedComponent {
/** /**
* Delete everything and hide the modal * Delete everything and hide the modal
*/ */

View File

@@ -1,16 +1,15 @@
import React from 'react'; import React from 'react';
import { findDOMNode } from 'react-dom'; import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
/** /**
* Export Modal * Export Modal
*/ */
export default class ModalExport extends TranslatedComponent { export default class ModalExport extends TranslatedComponent {
static propTypes = { static propTypes = {
title: React.PropTypes.string, title: PropTypes.string,
generator: React.PropTypes.func, generator: PropTypes.func,
data: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.object, React.PropTypes.array]) data: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.array])
}; };
/** /**
@@ -45,10 +44,9 @@ export default class ModalExport extends TranslatedComponent {
* Focus on textarea and select all * Focus on textarea and select all
*/ */
componentDidMount() { componentDidMount() {
let e = findDOMNode(this.refs.exportField); if (this.exportField) {
if (e) { this.exportField.focus();
e.focus(); this.exportField.select();
e.select();
} }
} }
@@ -68,7 +66,7 @@ export default class ModalExport extends TranslatedComponent {
<h2>{translate(this.props.title || 'Export')}</h2> <h2>{translate(this.props.title || 'Export')}</h2>
{description} {description}
<div> <div>
<textarea className='cb json' ref='exportField' readOnly value={this.state.exportJson} /> <textarea className='cb json' ref={node => this.exportField = node} readOnly value={this.state.exportJson} />
</div> </div>
<button className='r dismiss cap' onClick={this.context.hideModal}>{translate('close')}</button> <button className='r dismiss cap' onClick={this.context.hideModal}>{translate('close')}</button>
</div>; </div>;

View File

@@ -0,0 +1,28 @@
/* eslint react/no-danger: 0 */
import PropTypes from 'prop-types';
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
/**
* Help Modal
*/
export default class ModalHelp extends TranslatedComponent {
static propTypes = {
title: PropTypes.string
};
/**
* Render the modal
* @return {React.Component} Modal Content
*/
render() {
const translate = this.context.language.translate;
const text = translate('HELP_TEXT');
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2>{translate(this.props.title || 'Help')}</h2>
<div dangerouslySetInnerHTML={{ __html: text }} />
<button className='r dismiss cap' onClick={this.context.hideModal}>{translate('close')}</button>
</div>;
}
}

View File

@@ -1,91 +1,25 @@
import React from 'react'; import React from 'react';
import { findDOMNode } from 'react-dom'; import PropTypes from 'prop-types';
import cn from 'classnames'; import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import Router from '../Router';
import Persist from '../stores/Persist'; import Persist from '../stores/Persist';
import { Ships } from 'coriolis-data/dist'; import autoBind from 'auto-bind';
import Ship from '../shipyard/Ship'; import { isArray } from 'lodash';
import { ModuleNameToGroup, Insurance } from '../shipyard/Constants'; import { Ship } from 'ed-forge';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import { fromDetailedBuild } from '../shipyard/Serializer';
import { Download } from './SvgIcons';
import { outfitURL } from '../utils/UrlGenerators';
const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n'); const STATE = {
const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)'); READY: 0,
const mountMap = { 'H': 4, 'L': 3, 'M': 2, 'S': 1, 'U': 0 }; PARSED: 1,
const standardMap = { 'RB': 0, 'TM': 1, 'FH': 2, 'EC': 3, 'PC': 4, 'SS': 5, 'FS': 6 }; ERROR: 2,
const bhMap = { 'lightweight alloy': 0, 'reinforced alloy': 1, 'military grade composite': 2, 'mirrored surface composite': 3, 'reactive surface composite': 4 }; };
/**
* Check is slot is empty
* @param {Object} slot Slot model
* @return {Boolean} True if empty
*/
function isEmptySlot(slot) {
return slot.maxClass == this && slot.m === null;
}
/**
* Determine if a build is valid
* @param {string} shipId Ship ID
* @param {string} code Serialzied ship build 'code'
* @param {string} name Build name
* @throws {string} If build is not valid
*/
function validateBuild(shipId, code, name) {
let shipData = Ships[shipId];
if (!shipData) {
throw '"' + shipId + '" is not a valid Ship Id!';
}
if (typeof name != 'string' || name.length == 0) {
throw shipData.properties.name + ' build "' + name + '" must be a string at least 1 character long!';
}
if (typeof code != 'string' || code.length < 10) {
throw shipData.properties.name + ' build "' + name + '" is not valid!';
}
try {
let ship = new Ship(shipId, shipData.properties, shipData.slots);
ship.buildFrom(code);
} catch (e) {
throw shipData.properties.name + ' build "' + name + '" is not valid!';
}
}
/**
* Convert a ship-loadout JSON object to a Coriolis build
* @param {Object} detailedBuild ship-loadout
* @return {Object} Coriolis build
*/
function detailedJsonToBuild(detailedBuild) {
let ship;
if (!detailedBuild.name) {
throw 'Build Name missing!';
}
if (!detailedBuild.name.trim()) {
throw 'Build Name must be a string at least 1 character long!';
}
try {
ship = fromDetailedBuild(detailedBuild);
} catch (e) {
throw detailedBuild.ship + ' Build "' + detailedBuild.name + '": Invalid data';
}
return { shipId: ship.id, name: detailedBuild.name, code: ship.toString() };
}
/** /**
* Import Modal * Import Modal
*/ */
export default class ModalImport extends TranslatedComponent { export default class ModalImport extends TranslatedComponent {
static propTypes = { static propTypes = {
builds: React.PropTypes.object, // Optional: Import object importString: PropTypes.string, // Optional: Default data for import modal
builds: PropTypes.object, // Optional: Import object
}; };
/** /**
@@ -94,191 +28,36 @@ export default class ModalImport extends TranslatedComponent {
*/ */
constructor(props) { constructor(props) {
super(props); super(props);
autoBind(this);
this.state = { this.state = {
builds: props.builds, status: STATE.READY,
canEdit: !props.builds, builds: props.builds || [],
comparisons: null,
shipDiscount: null,
moduleDiscount: null,
errorMsg: null,
importString: null,
importValid: false,
insurance: null
}; };
this._process = this._process.bind(this);
this._import = this._import.bind(this);
this._importBackup = this._importBackup.bind(this);
this._importDetailedArray = this._importDetailedArray.bind(this);
this._importTextBuild = this._importTextBuild.bind(this);
this._validateImport = this._validateImport.bind(this);
} }
/** /**
* Import a Coriolis backup * Import SLEF formatted builds. Sets state to a map of the builds on success
* @param {Object} importData Backup Data * and flags if there was only a single build.
* @throws {string} If import fails *
*/ * @param {string} importData - Array of the list of builds.
_importBackup(importData) {
if (importData.builds && typeof importData.builds == 'object') {
for (let shipId in importData.builds) {
for (let buildName in importData.builds[shipId]) {
validateBuild(shipId, importData.builds[shipId][buildName], buildName);
}
}
this.setState({ builds: importData.builds });
} else {
throw 'builds must be an object!';
}
if (importData.comparisons) {
for (let compName in importData.comparisons) {
let comparison = importData.comparisons[compName];
for (let i = 0, l = comparison.builds.length; i < l; i++) {
let build = comparison.builds[i];
if (!importData.builds[build.shipId] || !importData.builds[build.shipId][build.buildName]) {
throw build.shipId + ' build "' + build.buildName + '" data is missing!';
}
}
}
this.setState({ comparisons: importData.comparisons });
}
// Check for old/deprecated discounts
if (importData.discounts instanceof Array && importData.discounts.length == 2) {
this.setState({ shipDiscount: importData.discounts[0], moduleDiscount: importData.discounts[1] });
}
// Check for ship discount
if (!isNaN(importData.shipDiscount)) {
this.setState({ shipDiscount: importData.shipDiscount * 1 });
}
// Check for module discount
if (!isNaN(importData.moduleDiscount)) {
this.setState({ shipDiscount: importData.moduleDiscount * 1 });
}
if (typeof importData.insurance == 'string') {
let insurance = importData.insurance.toLowerCase();
if (Insurance[insurance] !== undefined) {
this.setState({ insurance });
} else {
throw 'Invalid insurance type: ' + insurance;
}
}
}
/**
* Import an array of ship-loadout objects / builds
* @param {Array} importArr Array of ship-loadout JSON Schema builds
*/
_importDetailedArray(importArr) {
let builds = {};
for (let i = 0, l = importArr.length; i < l; i++) {
let build = detailedJsonToBuild(importArr[i]);
if (!builds[build.shipId]) {
builds[build.shipId] = {};
}
builds[build.shipId][build.name] = build.code;
}
this.setState({ builds });
}
/**
* Import a text build from ED Shipyard
* @param {string} buildStr Build string
* @throws {string} If parse / import fails * @throws {string} If parse / import fails
*/ */
_importTextBuild(buildStr) { _importSlefBuilds(importData) {
let buildName = textBuildRegex.exec(buildStr)[1].trim(); const builds = importData.reduce((memo, { data }) => {
let shipName = buildName.toLowerCase(); const shipModel = shipModelFromJson(data);
let shipId = null; const ship = shipFromLoadoutJSON(data);
const shipTemplate = Ships[shipModel];
const shipName = data.ShipName || shipTemplate.properties.name;
for (let sId in Ships) { const key = `Imported ${shipName}`;
if (Ships[sId].properties.name.toLowerCase() == shipName) { memo[shipModel] = {};
shipId = sId; memo[shipModel][key] = ship.toString();
break;
}
}
if (!shipId) { return memo;
throw 'No such ship found: "' + buildName + '"'; }, {});
}
let lines = buildStr.split('\n'); this.setState({ builds, singleBuild: Object.keys(builds).length === 1 });
let ship = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots);
ship.buildWith(null);
for (let i = 1; i < lines.length; i++) {
let line = lines[i].trim();
if (!line) { continue; }
if (line.substring(0, 3) == '---') { break; }
let parts = lineRegex.exec(line);
if (!parts) { throw 'Error parsing: "' + line + '"'; }
let typeSize = parts[1];
let cl = parts[2];
let rating = parts[3];
let mount = parts[4];
let missile = parts[5];
let name = parts[6].trim();
let slot, group;
if (isNaN(typeSize)) { // Standard or Hardpoint
if (typeSize.length == 1) { // Hardpoint
let slotClass = mountMap[typeSize];
if (cl > slotClass) { throw cl + rating + ' ' + name + ' exceeds slot size: "' + line + '"'; }
slot = ship.hardpoints.find(isEmptySlot, slotClass);
if (!slot) { throw 'No hardpoint slot available for: "' + line + '"'; }
group = ModuleNameToGroup[name.toLowerCase()];
let hp = ModuleUtils.findHardpoint(group, cl, rating, group ? null : name, mount, missile);
if (!hp) { throw 'Unknown component: "' + line + '"'; }
ship.use(slot, hp, true);
} else if (typeSize == 'BH') {
let bhId = bhMap[name.toLowerCase()];
if (bhId === undefined) { throw 'Unknown bulkhead: "' + line + '"'; }
ship.useBulkhead(bhId, true);
} else if (standardMap[typeSize] != undefined) {
let standardIndex = standardMap[typeSize];
if (ship.standard[standardIndex].maxClass < cl) { throw name + ' exceeds max class for the ' + ship.name; }
ship.use(ship.standard[standardIndex], ModuleUtils.standard(standardIndex, cl + rating), true);
} else {
throw 'Unknown component: "' + line + '"';
}
} else {
if (cl > typeSize) { throw cl + rating + ' ' + name + ' exceeds slot size: "' + line + '"'; }
slot = ship.internal.find(isEmptySlot, typeSize);
if (!slot) { throw 'No internal slot available for: "' + line + '"'; }
group = ModuleNameToGroup[name.toLowerCase()];
let intComp = ModuleUtils.findInternal(group, cl, rating, group ? null : name);
if (!intComp) { throw 'Unknown component: "' + line + '"'; }
ship.use(slot, intComp);
}
}
let builds = {};
builds[shipId] = {};
builds[shipId]['Imported ' + buildName] = ship.toString();
this.setState({ builds, singleBuild: true });
} }
/** /**
@@ -286,159 +65,56 @@ export default class ModalImport extends TranslatedComponent {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
* @throws {string} If validation fails * @throws {string} If validation fails
*/ */
_validateImport(event) { _parse(event) {
let importData = null; const importString = event.target.value.trim();
let importString = event.target.value.trim();
this.setState({
builds: null,
comparisons: null,
shipDiscount: null,
moduleDiscount: null,
errorMsg: null,
importValid: false,
insurance: null,
singleBuild: false,
importString,
});
if (!importString) {
return;
}
try { try {
if (textBuildRegex.test(importString)) { // E:D Shipyard build text let data = JSON.parse(importString);
this._importTextBuild(importString); if (!isArray(data)) {
} else { // JSON Build data data = [data];
importData = JSON.parse(importString);
if (!importData || typeof importData != 'object') {
throw 'Must be an object or array!';
}
if (importData instanceof Array) { // Must be detailed export json
this._importDetailedArray(importData);
} 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 { // Using Backup JSON
this._importBackup(importData);
}
} }
} catch (e) {
this.setState({ errorMsg: (typeof e == 'string') ? e : 'Cannot Parse the data!' });
return;
}
this.setState({ importValid: true }); const ships = data.map((item) => {
}; try {
return new Ship(item.data ? item.data : item);
} catch (err) {
return err;
}
});
this.setState({ ships, status: STATE.PARSED });
} catch (err) {
this.setState({ err, status: STATE.ERROR });
}
}
/** /**
* Process imported data * Process imported data
*/ */
_process() { _process() {
let builds = null, comparisons = null; for (const build of this.state.builds) {
if (!build instanceof Error) {
// If only importing a single build go straight to the outfitting page Persist.saveBuild(build.Ship, build.CoriolisBuildName || build.ShipName, build.compress());
if (this.state.singleBuild) {
builds = this.state.builds;
let shipId = Object.keys(builds)[0];
let name = Object.keys(builds[shipId])[0];
Router.go(outfitURL(shipId, builds[shipId][name], name));
return;
}
if (this.state.builds) {
builds = {}; // Create new builds object such that orginal name retained, but can be renamed
for (let shipId in this.state.builds) {
let shipbuilds = this.state.builds[shipId];
builds[shipId] = {};
for (let buildName in shipbuilds) {
builds[shipId][buildName] = {
code: shipbuilds[buildName],
useName: buildName
};
}
} }
} }
this.setState({ builds: [], status: STATE.READY });
if (this.state.comparisons) { }
comparisons = {};
for (let name in this.state.comparisons) {
comparisons[name] = Object.assign({ useName: name }, this.state.comparisons[name]);
}
}
this.setState({ processed: true, builds, comparisons });
};
/**
* Import parsed, processed data and save
*/
_import() {
let state = this.state;
if (state.builds) {
let builds = state.builds;
for (let shipId in builds) {
for (let buildName in builds[shipId]) {
let build = builds[shipId][buildName];
let name = build.useName.trim();
if (name) {
Persist.saveBuild(shipId, name, build.code);
}
}
}
}
if (state.comparisons) {
let comparisons = state.comparisons;
for (let comp in comparisons) {
let comparison = comparisons[comp];
let useName = comparison.useName.trim();
if (useName) {
Persist.saveComparison(useName, comparison.builds, comparison.facets);
}
}
}
if (state.shipDiscount !== undefined) {
Persist.setShipDiscount(state.shipDiscount);
}
if (state.moduleDiscount !== undefined) {
Persist.setModuleDiscount(state.moduleDiscount);
}
if (state.insurance) {
Persist.setInsurance(state.insurance);
}
this.context.hideModal();
};
/** /**
* Capture build name changes * Capture build name changes
* @param {Object} item Build/Comparison import object * @param {Object} index Build/Comparison import object
* @param {SyntheticEvent} e Event * @param {SyntheticEvent} event Event
*/ */
_changeName(item, e) { _changeName(index, event) {
item.useName = e.target.value; const { builds } = this.state;
this.forceUpdate(); builds[index].CoriolisBuildName = event.target.value.trim();
this.setState({ builds });
} }
/**
* If imported data is already provided process immediately on mount
*/
componentWillMount() {
if (this.props.builds) {
this._process();
}
}
/** /**
* If textarea is shown focus on mount * If textarea is shown focus on mount
*/ */
componentDidMount() { componentDidMount() {
if (!this.props.builds && findDOMNode(this.refs.importField)) { if (!this.props.builds && this.importField) {
findDOMNode(this.refs.importField).focus(); this.importField.focus();
} }
} }
@@ -447,100 +123,54 @@ export default class ModalImport extends TranslatedComponent {
* @return {React.Component} Modal contents * @return {React.Component} Modal contents
*/ */
render() { render() {
let translate = this.context.language.translate; const { translate } = this.context.language;
let state = this.state; const { status, builds, err } = this.state;
let importStage;
if (!state.processed) { const buildRows = builds.map((build, i) => {
importStage = ( if (build instanceof Error) {
<div> return <tr key={i} className='cb'>
<textarea className='cb json' ref='importField' onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} /> <td colSpan={3} className='warning'>Error: {build.name}</td>
<button id='proceed' className='l cap' onClick={this._process} disabled={!state.importValid} >{translate('proceed')}</button> </tr>;
<div className='l warning' style={{ marginLeft:'3em' }}>{state.errorMsg}</div>
</div>
);
} else {
let comparisonTable, edit, buildRows = [];
if (state.comparisons) {
let comparisonRows = [];
for (let name in state.comparisons) {
let comparison = state.comparisons[name];
let hasComparison = Persist.hasComparison(comparison.useName);
comparisonRows.push(
<tr key={name} className='cb'>
<td>
<input type='text' onChange={this._changeName.bind(this, comparison)} value={comparison.useName}/>
</td>
<td style={{ textAlign:'center' }} className={ cn('cap', { warning: hasComparison, disabled: comparison.useName == '' }) }>
{translate(comparison.useName == '' ? 'skip' : (hasComparison ? 'overwrite' : 'create'))}
</td>
</tr>
);
}
comparisonTable = (
<table className='l' style={{ overflow:'hidden', margin: '1em 0', width: '100%' }} >
<thead>
<tr>
<th style={{ textAlign:'left' }}>{translate('comparison')}</th>
<th>{translate('action')}</th>
</tr>
</thead>
<tbody>
{comparisonRows}
</tbody>
</table>
);
} }
if(this.state.canEdit) { const exists = Persist.hasBuild(build.Ship, build.CoriolisBuildName);
edit = <button className='l cap' style={{ marginLeft: '2em' }} onClick={() => this.setState({ processed: false })}>{translate('edit data')}</button>; const saveName = build.CoriolisBuildName || build.ShipName;
} return <tr key={i} className='cb'>
<td>{translate(build.Ship)}</td>
let builds = this.state.builds; <td><input type='text' onChange={this._changeName.bind(this, i)} value={saveName}/></td>
for (let shipId in builds) { <td style={{ textAlign: 'center' }} className={cn('cap', { warning: exists, disabled: saveName === '' })}>
let shipBuilds = builds[shipId]; {translate(saveName === '' ? 'skip' : (exists ? 'overwrite' : 'create'))}
for (let buildName in shipBuilds) { </td>
let b = shipBuilds[buildName]; </tr>;
let hasBuild = Persist.hasBuild(shipId, b.useName); });
buildRows.push(
<tr key={shipId + buildName} className='cb'>
<td>{Ships[shipId].properties.name}</td>
<td><input type='text' onChange={this._changeName.bind(this, b)} value={b.useName}/></td>
<td style={{ textAlign: 'center' }} className={cn('cap', { warning: hasBuild, disabled: b.useName == '' })}>
{translate(b.useName == '' ? 'skip' : (hasBuild ? 'overwrite' : 'create'))}
</td>
</tr>
);
}
}
importStage = (
<div>
<table className='l' style={{ overflow:'hidden', margin: '1em 0', width: '100%' }}>
<thead>
<tr>
<th style={{ textAlign: 'left' }} >{translate('ship')}</th>
<th style={{ textAlign: 'left' }} >{translate('build name')}</th>
<th>{translate('action')}</th>
</tr>
</thead>
<tbody>
{buildRows}
</tbody>
</table>
{comparisonTable}
<button id='import' className='cl l' onClick={this._import}><Download/> {translate('import')}</button>
{edit}
</div>
);
}
return <div className='modal' onClick={ (e) => e.stopPropagation() }> return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2 >{translate('import')}</h2> <h2 >{translate('import')}</h2>
{importStage} <div>
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button> <textarea spellCheck={false} className='cb json' ref={node => this.importField = node} onChange={this._parse} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
{status === STATE.ERROR && <div className='l warning' style={{ marginLeft:'3em' }}>{err.toString()}</div>}
</div>
{builds.length && <div>
<table className='l' style={{ overflow:'hidden', margin: '1em 0', width: '100%' }}>
<thead>
<tr>
<th style={{ textAlign: 'left' }} >{translate('ship')}</th>
<th style={{ textAlign: 'left' }} >{translate('build name')}</th>
<th>{translate('action')}</th>
</tr>
</thead>
<tbody>
{buildRows}
</tbody>
</table>
</div>}
<button id='proceed' className='l cap' onClick={this._process}
disabled={status !== STATE.PARSED} >
{translate('proceed')}
</button>
<button className={'r dismiss cap'} onClick={this.context.hideModal}>
{translate('close')}
</button>
</div>; </div>;
} }
} }

View File

@@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import ShortenUrl from '../utils/ShortenUrl'; import ShortenUrl from '../utils/ShortenUrl';
@@ -6,9 +7,8 @@ import ShortenUrl from '../utils/ShortenUrl';
* Permalink modal * Permalink modal
*/ */
export default class ModalPermalink extends TranslatedComponent { export default class ModalPermalink extends TranslatedComponent {
static propTypes = { static propTypes = {
url: React.PropTypes.string.isRequired url: PropTypes.string.isRequired
}; };
/** /**
@@ -49,6 +49,7 @@ export default class ModalPermalink extends TranslatedComponent {
<h3 >{translate('shortened')}</h3> <h3 >{translate('shortened')}</h3>
<input value={this.state.shortenedUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/> <input value={this.state.shortenedUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/>
<br/><br/> <br/><br/>
<p>s.orbis.zone is the new URL shortener domain, old eddp.co urls are considered end of life and could go down at any moment. Sorry for any inconvenience.</p>
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button> <button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
</div>; </div>;
} }

View File

@@ -0,0 +1,108 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames';
import NumberEditor from 'react-number-editor';
import { Module } from 'ed-forge';
/**
* Modification
*/
export default class Modification extends TranslatedComponent {
static propTypes = {
highlight: PropTypes.bool,
m: PropTypes.instanceOf(Module).isRequired,
property: PropTypes.string.isRequired,
onSet: PropTypes.func.isRequired,
showProp: PropTypes.object,
onPropToggle: PropTypes.func.isRequired,
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
const { m, property, showProp } = props;
const { beneficial, unit, value } = m.getFormatted(property, true);
this.state = { beneficial, unit, value, showProp };
}
/**
* Notify listeners that a new value has been entered and commited.
*/
_updateFinished() {
const { onSet, m, property } = this.props;
const { inputValue } = this.state;
const numValue = Number(inputValue);
if (!isNaN(numValue) && this.state.value !== numValue) {
onSet(property, numValue);
const { beneficial, unit, value } = m.getFormatted(property, true);
this.setState({ beneficial, unit, value });
}
}
_toggleProperty() {
const { onPropToggle, property } = this.props;
const showProp = !this.state.showProp;
// TODO: defer until menu closed
onPropToggle(property, showProp);
this.setState({ showProp });
}
/**
* Render the modification
* @return {React.Component} modification
*/
render() {
const { formats } = this.context.language;
const { highlight, m, property } = this.props;
const { beneficial, unit, value, inputValue, showProp } = this.state;
// Some features only apply to specific modules; these features will be
// undefined on items that do not belong to the same class. Filter these
// features here
if (value === undefined) {
return null;
}
const { value: modifierValue, unit: modifierUnit } = m.getModifierFormatted(property);
return (
<tr>
<td>
<span>
<input type="checkbox" checked={showProp} onClick={() => this._toggleProperty()}/>
</span>
</td>
<td className="input-container">
<span>
<NumberEditor value={inputValue || value} stepModifier={1}
decimals={2} step={0.01} style={{ textAlign: 'right', width: '100%' }}
className={cn('cb', { 'greyed-out': !highlight })}
onKeyDown={(event) => {
if (event.key == 'Enter') {
this._updateFinished();
event.stopPropagation();
}
}}
onValueChange={(inputValue) => {
if (inputValue.length <= 15) {
this.setState({ inputValue });
}
}} />
</span>
</td>
<td style={{ textAlign: 'left' }}>
<span className="unit-container">{unit}</span>
</td>
<td style={{ textAlign: 'center' }}
className={cn({
secondary: beneficial,
warning: beneficial === false,
})}
>{formats.f2(modifierValue)}{modifierUnit || ''}</td>
</tr>
);
}
}

View File

@@ -0,0 +1,385 @@
import React from 'react';
import PropTypes from 'prop-types';
import { chain, flatMap, keys } from 'lodash';
import TranslatedComponent from './TranslatedComponent';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames';
import Modification from './Modification';
import {
blueprintTooltip,
specialToolTip
} from '../utils/BlueprintFunctions';
import { getBlueprintInfo, getExperimentalInfo } from 'ed-forge/lib/src/data/blueprints';
import { getModuleInfo } from 'ed-forge/lib/src/data/items';
import { SHOW } from '../shipyard/StatsMapping';
/**
* Modifications menu
*/
export default class ModificationsMenu extends TranslatedComponent {
static propTypes = {
className: PropTypes.string,
m: PropTypes.object.isRequired,
propsToShow: PropTypes.object.isRequired,
onPropToggle: PropTypes.func.isRequired,
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this);
this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this);
this.selectedModRef = null;
this.selectedSpecialRef = null;
const { m } = props;
this.state = {
blueprintProgress: m.getBlueprintProgress(),
blueprintMenuOpened: !m.getBlueprint(),
specialMenuOpened: false
};
}
/**
* Render the blueprints
* @return {Object} list: Array of React Components
*/
_renderBlueprints() {
const { m } = this.props;
const { language, tooltip, termtip } = this.context;
const { translate } = language;
const blueprints = m.getApplicableBlueprints().map(blueprint => {
const info = getBlueprintInfo(blueprint);
let blueprintGrades = keys(info.features).map(grade => {
// Grade is a string in the JSON so make it a number
grade = Number(grade);
const active = m.getBlueprint() === blueprint && m.getBlueprintGrade() === grade;
const key = blueprint + ':' + grade;
return <li key={key} data-id={key} className={cn('c', { active })}
style={{ width: '2em' }}
onMouseOver={termtip.bind(null, blueprintTooltip(language, m, blueprint, grade))}
onMouseOut={tooltip.bind(null, null)}
onClick={() => {
m.setBlueprint(blueprint, grade, 1);
this.setState({
blueprintMenuOpened: false,
specialMenuOpened: true,
});
}}
ref={active ? (ref) => { this.selectedModRef = ref; } : undefined}
>{grade}</li>;
});
return [
<div key={'div' + blueprint} className={'select-group cap'}>
{translate(blueprint)}
</div>,
<ul key={'ul' + blueprint}>{blueprintGrades}</ul>
];
});
return flatMap(blueprints);
}
/**
* Render the specials
* @param {Object} props React component properties
* @param {Object} context React component context
* @return {Object} list: Array of React Components
*/
_renderSpecials() {
const { m } = this.props;
const { language, tooltip, termtip } = this.context;
const translate = language.translate;
const applied = m.getExperimental();
const experimentals = [];
for (const experimental of m.getApplicableExperimentals()) {
const active = experimental === applied;
let specialTt = specialToolTip(language, m, experimental);
experimentals.push(
<div key={experimental} data-id={experimental}
style={{ cursor: 'pointer' }}
className={cn('button-inline-menu', { active })}
onClick={this._specialSelected(experimental)}
ref={active ? (ref) => { this.selectedSpecialRef = ref; } : undefined}
onMouseOver={termtip.bind(null, specialTt)}
onMouseOut={tooltip.bind(null, null)}
>{translate(experimental)}</div>
);
}
if (experimentals.length) {
experimentals.unshift(
<div style={{ cursor: 'pointer', fontWeight: 'bold' }}
className="button-inline-menu warning" key="none" data-id="none"
// Setting the special effect to undefined clears it
onClick={this._specialSelected(undefined)}
ref={!applied ? (ref) => { this.selectedSpecialRef = ref; } : undefined}
>{translate('PHRASE_NO_SPECIAL')}</div>
);
}
return experimentals;
}
/**
* Create a modification component
*/
_mkModification(property, highlight) {
const { translate } = this.context.language;
const { m, propsToShow, onPropToggle } = this.props;
let onSet = m.set.bind(m);
// Show resistance instead of effectiveness
const mapped = SHOW[property];
if (mapped) {
property = mapped.as;
onSet = mapped.setter.bind(undefined, m);
}
return [
<tr key={`th-${property}`}>
<th colSpan="4">
<span className="cb">{translate(property)}</span>
</th>
</tr>,
<Modification key={property} m={m} property={property}
onSet={onSet} highlight={highlight} showProp={propsToShow[property]}
onPropToggle={onPropToggle} />
];
}
/**
* Render the modifications
* @return {Array} Array of React Components
*/
_renderModifications() {
const { m } = this.props;
const blueprintFeatures = getBlueprintInfo(m.getBlueprint()).features[
m.getBlueprintGrade()
];
const blueprintModifications = chain(keys(blueprintFeatures))
.map((feature) => this._mkModification(feature, true))
.filter(([_, mod]) => Boolean(mod))
.flatMap()
.value();
const moduleModifications = chain(keys(getModuleInfo(m.getItem()).props))
.filter((prop) => !blueprintFeatures[prop])
.map((prop) => this._mkModification(prop, false))
.flatMap()
.value();
return blueprintModifications.concat(moduleModifications);
}
/**
* Toggle the blueprints menu
*/
_toggleBlueprintsMenu() {
this.setState({ blueprintMenuOpened: !this.state.blueprintMenuOpened });
}
/**
* Toggle the specials menu
*/
_toggleSpecialsMenu() {
this.setState({ specialMenuOpened: !this.state.specialMenuOpened });
}
/**
* Creates a callback for when a special effect is being selected
* @param {string} special The name of the selected special
* @returns {function} Callback
*/
_specialSelected(special) {
return () => {
const { m } = this.props;
m.setExperimental(special);
this.setState({ specialMenuOpened: false });
};
}
/**
* Set focus on first element in modifications menu
* if component updates, unless update is due to value change
* in a modification
*/
componentDidUpdate() {
if (this.selectedModRef) {
this.selectedModRef.focus();
return;
} else if (this.selectedSpecialRef) {
this.selectedSpecialRef.focus();
return;
}
}
/**
* Render the list
* @return {React.Component} List
*/
render() {
const { language, tooltip, termtip } = this.context;
const translate = language.translate;
const { m } = this.props;
const {
blueprintProgress, blueprintMenuOpened, specialMenuOpened,
} = this.state;
const appliedBlueprint = m.getBlueprint();
const appliedGrade = m.getBlueprintGrade();
const appliedExperimental = m.getExperimental();
let renderComponents = [];
switch (true) {
case !appliedBlueprint || blueprintMenuOpened:
renderComponents = this._renderBlueprints();
break;
case specialMenuOpened:
renderComponents = this._renderSpecials();
break;
default:
// Since the first case didn't apply, there is a blueprint applied so
// we render the modifications
let blueprintTt = blueprintTooltip(language, m, appliedBlueprint, appliedGrade);
renderComponents.push(
<div style={{ cursor: 'pointer' }} key="blueprintsMenu"
className="section-menu button-inline-menu"
onMouseOver={termtip.bind(null, blueprintTt)}
onMouseOut={tooltip.bind(null, null)}
onClick={this._toggleBlueprintsMenu}
>
{translate(appliedBlueprint)} {translate('grade')} {appliedGrade}
</div>
);
if (m.getApplicableExperimentals().length) {
let specialLabel = translate('PHRASE_SELECT_SPECIAL');
let specialTt;
if (appliedExperimental) {
specialLabel = appliedExperimental;
specialTt = specialToolTip(language, m, appliedExperimental);
}
renderComponents.push(
<div className="section-menu button-inline-menu"
style={{ cursor: 'pointer' }}
onMouseOver={specialTt ? termtip.bind(null, specialTt) : null}
onMouseOut={specialTt ? tooltip.bind(null, null) : null}
onClick={this._toggleSpecialsMenu}
>{specialLabel}</div>
);
}
renderComponents.push(
<div
className="section-menu button-inline-menu warning"
style={{ cursor: 'pointer' }}
onClick={() => {
m.resetEngineering();
this.selectedModRef = null;
this.selectedSpecialRef = null;
tooltip(null);
this.setState({
blueprintMenuOpened: true,
blueprintProgress: undefined,
});
}}
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')}
onMouseOut={tooltip.bind(null, null)}
>{translate('reset')}</div>,
<table style={{ width: '100%', backgroundColor: 'transparent' }}>
<tbody>
<tr>
<td
className={cn(
'section-menu button-inline-menu',
{ active: false },
)}
>{translate('mroll')}:</td>
<td
className={cn(
'section-menu button-inline-menu',
{ active: blueprintProgress === 0 },
)} style={{ cursor: 'pointer' }}
onClick={() => {
m.setBlueprintProgress(0);
this.setState({ blueprintProgress: 0 });
}}
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')}
onMouseOut={tooltip.bind(null, null)}
>{translate('0%')}</td>
<td
className={cn(
'section-menu button-inline-menu',
{ active: blueprintProgress === 0.5 },
)} style={{ cursor: 'pointer' }}
onClick={() => {
m.setBlueprintProgress(0.5);
this.setState({ blueprintProgress: 0.5 });
}}
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_FIFTY')}
onMouseOut={tooltip.bind(null, null)}
>{translate('50%')}</td>
<td
className={cn(
'section-menu button-inline-menu',
{ active: blueprintProgress === 1 },
)}
style={{ cursor: 'pointer' }}
onClick={() => {
m.setBlueprintProgress(1);
this.setState({ blueprintProgress: 1 });
}}
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')}
onMouseOut={tooltip.bind(null, null)}
>{translate('100%')}</td>
<td
className={cn(
'section-menu button-inline-menu',
{ active: blueprintProgress % 0.5 !== 0 },
)}
style={{ cursor: 'pointer' }}
onClick={() => {
const blueprintProgress = Math.random();
m.setBlueprintProgress(blueprintProgress);
this.setState({ blueprintProgress });
}}
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')}
onMouseOut={tooltip.bind(null, null)}
>{translate('random')}</td>
</tr>
</tbody>
</table>,
<hr />,
<span
onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')}
onMouseOut={tooltip.bind(null, null)}
>
<table style={{ width: '100%' }}>
<tbody>
{this._renderModifications()}
</tbody>
</table>
</span>
);
}
return (
<div className={cn('select', this.props.className)}
onClick={(e) => e.stopPropagation()}
onContextMenu={stopCtxPropagation}
>
{renderComponents}
</div>
);
}
}

View File

@@ -0,0 +1,59 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { ShipProps } from 'ed-forge';
const { SPEED, BOOST_SPEED, ROLL, BOOST_ROLL, YAW, BOOST_YAW, PITCH, BOOST_PITCH } = ShipProps;
/**
* Movement
*/
export default class Movement extends TranslatedComponent {
static propTypes = {
code: PropTypes.string.isRequired,
ship: PropTypes.object.isRequired,
boost: PropTypes.bool.isRequired,
pips: PropTypes.object.isRequired,
};
/**
* Render movement
* @return {React.Component} contents
*/
render() {
const { ship, boost } = this.props;
const { language } = this.context;
const { formats } = language;
return (
<span id='movement'>
<svg viewBox='0 0 600 600' fillRule="evenodd" clipRule="evenodd">
{/* Axes */}
<path d="M150 250v300" strokeWidth='1'/>
<path d="M150 250l236 236" strokeWidth='1'/>
<path d="M150 250l350 -200" strokeWidth='1'/>
{/* End Arrow */}
<path d="M508 43.3L487 67l-10-17.3 31-6.4z"/>
{/* Axes arcs and arrows */}
<path d="M71.7 251.7C64.2 259.2 60 269.4 60 280c0 22 18 40 40 40s40-18 40-40c0-10.6-4.2-20.8-11.7-28.3 7.5 7.5 11.7 17.7 11.7 28.3 0 22-18 40-40 40s-40-18-40-40c0-10.6 4.2-20.8 11.7-28.3z" strokeWidth='4' transform="matrix(.6 0 0 .3 87.5 376.3)"/>
<path d="M142.8 453l-13.2 8.7-2.6-9.7 15.8 1z"/>
<path d="M144.7 451.6l.5 1.6-16.2 10.6h-.4l-3.5-13 .7-.4 19.3 1.2zm-14.2 7.7l7.7-5-9.2-.7 1.5 5.7zm25.7-6.3l15.8-1-2.6 9.7-13.2-8.8z"/>
<path d="M174 450.8l-3.6 13h-.4l-16.2-10.6.5-1.6 19.3-1.2.3.4zm-13.2 3.4l7.7 5 1.5-5.6-9.2.6z"/>
<path d="M407.7 119c2 .7 4.3 1 6.4 1 14 0 25-11.2 25-25s-11-25-25-25c-11 0-21 7.6-24 18.5 3-11 13-18.5 24-18.5 14 0 25 11.2 25 25s-11 25-25 25c-2 0-4-.3-6-1z" strokeWidth='2'/>
<path d="M388 99.7L387 84l9.8 2.5-8.7 13.2z"/>
<path d="M398.8 85.5l.2.5-10.7 16-1.6-.3-1.2-19.3.4-.3 12.5 3.8zm-9.5 9.7l5-7.7-5.6-1.6.6 9zm10 20.8l15.7-1-2.6 9.7-13.2-8.8z"/>
<path d="M417 113.8l-3.6 13h-.4l-16.2-10.6.5-1.6 19.3-1.2.3.4zm-13.2 3.4l7.7 5 1.5-5.6-9.2.6z"/>
<path d="M355 430c0-13.8-11.2-25-25-25s-25 11.2-25 25 11.2 25 25 25c-13.8 0-25-11.2-25-25s11.2-25 25-25 25 11.2 25 25z" strokeWidth='2'/>
<path d="M357 439.7l-8.8-13 9.7-2.7-1 15.7z"/>
<path d="M359.5 422.4l-1.2 19.3-1.6.4-10.7-16 .2-.2 13-3.4.3.4zm-9 5l5.2 7.8.6-9.3-5.7 1.2zm-10.5 24l-13.2 8.6-2.6-9.7 15.8 1z"/>
<path d="M342 450l.4 1.5-16.2 10.7-.4-.2-3.5-13 .3-.3L342 450zm-14.3 7.6l7.7-5-9.2-.6 1.5 5.6z"/>
<text x="470" y="30" strokeWidth='0'>{formats.int(ship.get(boost ? BOOST_SPEED : SPEED)) + 'm/s'}</text>
<text x="355" y="410" strokeWidth='0'>{formats.int(ship.get(boost ? BOOST_PITCH : PITCH)) + '°/s'}</text>
<text x="450" y="110" strokeWidth='0'>{formats.int(ship.get(boost ? BOOST_ROLL : ROLL)) + '°/s'}</text>
<text x="160" y="430" strokeWidth='0'>{formats.int(ship.get(boost ? BOOST_YAW : YAW)) + '°/s'}</text>
</svg>
</span>);
}
}

View File

@@ -0,0 +1,419 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import PieChart from './PieChart';
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import { Ship } from 'ed-forge';
import autoBind from 'auto-bind';
import { DAMAGE_METRICS } from 'ed-forge/lib/src/ship-stats';
import { clone, mapValues, mergeWith, reverse, sortBy, sum, toPairs, values } from 'lodash';
/**
* Turns an object into a tooltip.
* @param {function} translate Translate function
* @param {object} o Map to make the tooltip from
* @returns {React.Component} Tooltip
*/
function objToTooltip(translate, o) {
return toPairs(o)
.filter(([k, v]) => Boolean(v))
.map(([k, v]) => <div key={k}>{`${translate(k)}: ${v}`}</div>);
}
/**
* Returns a data object used by {@link PieChart} that shows damage by type.
* @param {function} translate Translation function
* @param {Calc.SDps} o Object that holds sdps split up by type
* @returns {Object} Data object
*/
function objToPie(translate, o) {
return toPairs(o).map(([k, value]) => {
return { label: translate(k), value };
});
}
/**
* Offence information
* Offence information consists of four panels:
* - textual information (time to drain cap, time to take down shields etc.)
* - breakdown of damage sources (pie chart)
* - comparison of shield resistances (table chart)
* - effective sustained DPS of weapons (bar chart)
*/
export default class Offence extends TranslatedComponent {
static propTypes = {
code: PropTypes.string.isRequired,
ship: PropTypes.instanceOf(Ship).isRequired,
opponent: PropTypes.instanceOf(Ship).isRequired,
engagementRange: PropTypes.number.isRequired,
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
autoBind(this);
this.state = {
predicate: 'classRating',
desc: true,
};
}
/**
* Set the sort order and sort
* @param {string} predicate Sort predicate
*/
_sortOrder(predicate) {
let desc = predicate == this.state.predicate ? !this.state.desc : true;
this.setState({ predicate, desc });
}
/**
* Render offence
* @return {React.Component} contents
*/
render() {
const { ship } = this.props;
const { language, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const sortOrder = this._sortOrder;
const {
drained, sustained, rangeMultiplier, hardnessMultiplier, timeToDrain
} = ship.getMetrics(DAMAGE_METRICS);
const portions = {
Absolute: sustained.types.abs,
Explosive: sustained.types.expl,
Kinetic: sustained.types.kin,
Thermic: sustained.types.therm,
};
const oppShield = ship.getOpponent().getShield();
const shieldMults = {
Absolute: 1,
Explosive: oppShield.explosive.damageMultiplier,
Kinetic: oppShield.kinetic.damageMultiplier,
Thermic: oppShield.thermal.damageMultiplier,
};
const oppArmour = ship.getOpponent().getArmour();
const armourMults = {
Absolute: 1,
Explosive: oppArmour.explosive.damageMultiplier,
Kinetic: oppArmour.kinetic.damageMultiplier,
Thermic: oppArmour.thermal.damageMultiplier,
};
const weapons = sortBy(ship.getHardpoints(), (m) => m.get('distributordraw'));
let rows = weapons.map((weapon) => {
const sdps = weapon.get('sustaineddamagepersecond');
const byRange = weapon.getRangeEffectiveness();
const weaponPortions = {
Absolute: weapon.get('absolutedamageportion'),
Explosive: weapon.get('explosivedamageportion'),
Kinetic: weapon.get('kineticdamageportion'),
Thermic: weapon.get('thermicdamageportion'),
};
const baseSdpsTooltip = objToTooltip(
translate,
mapValues(weaponPortions, (p) => formats.f1(sdps * p)),
);
const bySys = oppShield.absolute.bySys;
const shieldResEfts = mergeWith(
clone(weaponPortions),
shieldMults,
(objV, srcV) => objV * srcV
);
const byShieldRes = sum(values(shieldResEfts));
const shieldsSdpsTooltip = objToTooltip(
translate,
mapValues(
shieldResEfts,
(mult) => formats.f1(byRange * mult * bySys * sdps),
),
);
const shieldsEftTooltip = objToTooltip(
translate,
{
range: formats.pct1(byRange),
resistance: formats.pct1(byShieldRes),
'power distributor': formats.pct1(bySys),
},
);
const shieldEft = byRange * byShieldRes * bySys;
const byHardness = weapon.getArmourEffectiveness();
const armourResEfts = mergeWith(
clone(weaponPortions),
armourMults,
(objV, srcV) => objV * srcV,
);
const byArmourRes = sum(values(armourResEfts));
const armourSdpsTooltip = objToTooltip(
translate,
mapValues(
armourResEfts,
(mult) => formats.f1(byRange * mult * byHardness * sdps)
),
);
const armourEftTooltip = objToTooltip(
translate,
{
range: formats.pct1(byRange),
resistance: formats.pct1(byArmourRes),
hardness: formats.pct1(byHardness),
},
);
const armourEft = byRange * byArmourRes * byHardness;
const bp = weapon.getBlueprint();
const grade = weapon.getBlueprintGrade();
const exp = weapon.getExperimental();
let bpTitle = `${translate(bp)} ${translate('grade')} ${grade}`;
if (exp) {
bpTitle += `, ${translate(exp)}`;
}
return {
slot: weapon.getSlot(),
mount: weapon.mount,
classRating: weapon.getClassRating(),
type: weapon.readMeta('type'),
bpTitle: bp ? ` (${bpTitle})` : null,
sdps,
baseSdpsTooltip,
shieldSdps: sdps * shieldEft,
shieldEft,
shieldsSdpsTooltip,
shieldsEftTooltip,
armourSdps: sdps * armourEft,
armourEft,
armourSdpsTooltip,
armourEftTooltip,
};
});
const { predicate, desc } = this.state;
rows = sortBy(rows, (row) => row[predicate]);
if (desc) {
reverse(rows);
}
const sdpsTooltip = objToTooltip(
translate,
mapValues(portions, (p) => formats.f1(sustained.dps * p)),
);
const sdpsPie = objToPie(
translate,
mapValues(portions, (p) => Math.round(sustained.dps * p)),
);
const shieldSdpsSrcs = mergeWith(
clone(portions),
shieldMults,
(objV, srcV) => sustained.dps * oppShield.absolute.bySys *
rangeMultiplier * objV * srcV,
);
const shieldsSdps = sum(values(shieldSdpsSrcs));
const shieldsSdpsTooltip = objToTooltip(
translate,
mapValues(shieldSdpsSrcs, (v) => formats.f1(v)),
);
const shieldsSdpsPie = objToPie(
translate,
mapValues(shieldSdpsSrcs, (v) => Math.round(v)),
);
const armourSdpsSrcs = mergeWith(
clone(portions),
armourMults,
(objV, srcV) => sustained.dps * hardnessMultiplier * rangeMultiplier *
objV * srcV,
);
const armourSdps = sum(values(armourSdpsSrcs));
const totalArmourSDpsTooltipDetails = objToTooltip(
translate,
mapValues(armourSdpsSrcs, (v) => formats.f1(v)),
);
const armourSDpsData = objToPie(
translate,
mapValues(armourSdpsSrcs, (v) => Math.round(v)),
);
const drainedPortions = {
Absolute: drained.types.abs,
Explosive: drained.types.expl,
Kinetic: drained.types.kin,
Thermic: drained.types.therm,
};
// How much damage do we deal, before the capacitor is empty?
const armourLeft = oppArmour.armour - (timeToDrain * armourSdps);
// If we can't kill the enemy on one capacitor, factor in drained damage
let timeToDepleteArmour;
if (armourLeft > 0) {
const effectiveDrainedDps = sum(values(mergeWith(
clone(drainedPortions),
armourMults,
(objV, srcV) => objV * srcV,
))) * drained.dps * rangeMultiplier *
hardnessMultiplier;
timeToDepleteArmour = effectiveDrainedDps === 0 ? Infinity :
timeToDrain + (armourLeft / effectiveDrainedDps);
} else {
timeToDepleteArmour = oppArmour.armour / armourSdps;
}
// How much damage do we deal, before the capacitor is empty?
const shieldsLeft = oppShield.withSCBs - (timeToDrain * shieldsSdps);
// If we can't kill the enemy on one capacitor, factor in drained damage
let timeToDepleteShields;
if (shieldsLeft > 0) {
const effectiveDrainedDps = sum(values(mergeWith(
clone(drainedPortions),
shieldMults,
(objV, srcV) => objV * srcV,
))) * drained.dps * rangeMultiplier;
timeToDepleteShields = effectiveDrainedDps === 0 ? Infinity :
timeToDrain + (shieldsLeft / effectiveDrainedDps);
} else {
timeToDepleteShields = oppShield.withSCBs / shieldsSdps;
}
return (
<span id='offence'>
<div className='group full'>
<table>
<thead>
<tr className='main'>
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'classRating')}>{translate('weapon')}</th>
<th colSpan='1'>{translate('overall')}</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, 'sdps')}>sdps</th>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')}
onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'shieldSdps')}>sdps</th>
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_SHIELDS')}
onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'shieldEft')}>eft</th>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')}
onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'armourSdps')}>sdps</th>
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')}
onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'armourEft')}>eft</th>
</tr>
</thead>
<tbody>
{rows.map((row) => (
<tr key={row.slot}>
<td className='ri'>
{row.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed className='icon'/></span> : null}
{row.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : null}
{row.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : null}
{row.classRating} {translate(row.type)}
{row.bpTitle}
</td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, row.baseSdpsTooltip)}
onMouseOut={tooltip.bind(null, null)}
>{formats.f1(row.sdps)}</span></td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, row.shieldsSdpsTooltip)}
onMouseOut={tooltip.bind(null, null)}
>{formats.f1(row.shieldSdps)}</span></td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, row.shieldsEftTooltip)}
onMouseOut={tooltip.bind(null, null)}
>{formats.pct1(row.shieldEft)}</span></td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, row.armourSdpsTooltip)}
onMouseOut={tooltip.bind(null, null)}
>{formats.f1(row.armourSdps)}</span></td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, row.armourEftTooltip)}
onMouseOut={tooltip.bind(null, null)}
>{formats.pct1(row.armourEft)}</span></td>
</tr>
))}
{rows.length > 0 &&
<tr>
<td></td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, sdpsTooltip)} onMouseOut={tooltip.bind(null, null)}>
={formats.f1(sustained.dps)}
</span>
</td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, shieldsSdpsTooltip)} onMouseOut={tooltip.bind(null, null)}>
={formats.f1(shieldsSdps)}
</span>
</td>
<td></td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, totalArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>
={formats.f1(armourSdps)}
</span>
</td>
<td></td>
<td></td>
</tr>
}
</tbody>
</table>
</div>
<div className='group quarter'>
<h2>{translate('offence metrics')}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_DRAIN_WEP'))}
onMouseOut={tooltip.bind(null, null)}>
{translate('PHRASE_TIME_TO_DRAIN_WEP')}<br/>
{timeToDrain === Infinity ? translate('never') : formats.time(timeToDrain)}
</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_SHIELDS'))}
onMouseOut={tooltip.bind(null, null)}>
{translate('PHRASE_EFFECTIVE_SDPS_SHIELDS')}<br/>
{formats.f1(shieldsSdps)}
</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_SHIELDS'))}
onMouseOut={tooltip.bind(null, null)}>
{translate('PHRASE_TIME_TO_REMOVE_SHIELDS')}<br/>
{timeToDepleteShields === Infinity ? translate('never') : formats.time(timeToDepleteShields)}
</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_ARMOUR'))}
onMouseOut={tooltip.bind(null, null)}>
{translate('PHRASE_EFFECTIVE_SDPS_ARMOUR')}<br/>
{formats.f1(armourSdps)}
</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_ARMOUR'))}
onMouseOut={tooltip.bind(null, null)}>
{translate('PHRASE_TIME_TO_REMOVE_ARMOUR')}<br/>
{timeToDepleteArmour === Infinity ? translate('never') : formats.time(timeToDepleteArmour)}
</h2>
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_OVERALL_DAMAGE'))}
onMouseOut={tooltip.bind(null, null)}>
{translate('overall damage')}
</h2>
<PieChart data={sdpsPie} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_DAMAGE'))}
onMouseOut={tooltip.bind(null, null)}>
{translate('shield damage sources')}
</h2>
<PieChart data={shieldsSdpsPie} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_ARMOUR_DAMAGE'))}
onMouseOut={tooltip.bind(null, null)}>
{translate('armour damage sources')}
</h2>
<PieChart data={armourSDpsData} />
</div>
</span>);
}
}

View File

@@ -0,0 +1,169 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import Persist from '../stores/Persist';
import TranslatedComponent from './TranslatedComponent';
import PowerManagement from './PowerManagement';
import CostSection from './CostSection';
import EngineProfile from './EngineProfile';
import FSDProfile from './FSDProfile';
import Movement from './Movement';
import Offence from './Offence';
import Defence from './Defence';
import WeaponDamageChart from './WeaponDamageChart';
import Pips from '../components/Pips';
import Boost from '../components/Boost';
import Fuel from '../components/Fuel';
import Cargo from '../components/Cargo';
import ShipPicker from '../components/ShipPicker';
import EngagementRange from '../components/EngagementRange';
import autoBind from 'auto-bind';
import { ShipProps } from 'ed-forge';
const { CARGO_CAPACITY, FUEL_CAPACITY } = ShipProps;
/**
* Outfitting subpages
*/
export default class OutfittingSubpages extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
code: PropTypes.string.isRequired,
buildName: PropTypes.string,
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
autoBind(this);
this.props.ship.setOpponent(this.props.ship);
this.state = {
boost: false,
cargo: props.ship.get(CARGO_CAPACITY),
fuel: props.ship.get(FUEL_CAPACITY),
pips: props.ship.getDistributorSettingsObject(),
tab: Persist.getOutfittingTab() || 'power',
engagementRange: 1000,
opponent: this.props.ship,
};
}
/**
* Show selected tab
* @param {string} tab Tab name
*/
_showTab(tab) {
Persist.setOutfittingTab(tab);
this.setState({ tab });
}
/**
* Render the section
* @return {React.Component} Contents
*/
render() {
const { buildName, code, ship } = this.props;
const { boost, cargo, fuel, pips, tab, engagementRange, opponent } = this.state;
const { translate } = this.context.language;
const cargoCapacity = ship.get(CARGO_CAPACITY);
const showCargoSlider = cargoCapacity > 0;
return (
<div>
{/* Control of ship and opponent */}
<div className="group quarter">
<h2 style={{ verticalAlign: 'middle', textAlign: 'center' }}>
{translate('ship control')}
</h2>
<Boost boost={boost} onChange={(boost) => this.setState({ boost })} />
</div>
<div className="group quarter">
<h2 style={{ verticalAlign: 'middle', textAlign: 'center' }}>
{translate('opponent')}
</h2>
<ShipPicker ship={ship} onChange={(opponent) => this.setState({ opponent })} />
</div>
<div className={cn('group', { quarter: showCargoSlider, half: !showCargoSlider })}>
<Fuel fuelCapacity={ship.get(FUEL_CAPACITY)} fuel={fuel}
onChange={(fuel) => this.setState({ fuel })} />
</div>
{showCargoSlider ?
<div className="group quarter">
<Cargo cargoCapacity={cargoCapacity} cargo={cargo}
onChange={(cargo) => this.setState({ cargo })} />
</div> : null}
<div className="group half">
<Pips ship={ship} pips={pips} onChange={(pips) => this.setState({ pips })} />
</div>
<div className="group half">
<EngagementRange ship={ship} engagementRange={engagementRange}
onChange={(engagementRange) => this.setState({ engagementRange })} />
</div>
<div className='group full' style={{ minHeight: '1000px' }}>
<table className='tabs'>
{/* Select tab section */}
<thead>
<tr>
<th style={{ width:'25%' }} className={cn({ active: tab == 'power' })}
onClick={this._showTab.bind(this, 'power')}>
{translate('power and costs')}
</th>
<th style={{ width:'25%' }} className={cn({ active: tab == 'profiles' })}
onClick={this._showTab.bind(this, 'profiles')}>
{translate('profiles')}</th>
<th style={{ width:'25%' }} className={cn({ active: tab == 'offence' })}
onClick={this._showTab.bind(this, 'offence')}>
{translate('offence')}
</th>
<th style={{ width:'25%' }} className={cn({ active: tab == 'defence' })}
onClick={this._showTab.bind(this, 'defence')}>
{translate('tab_defence')}
</th>
</tr>
</thead>
</table>
{/* Show selected tab */}
{tab == 'power' ?
<div>
<PowerManagement ship={ship} code={code} />
<CostSection ship={ship} buildName={buildName} code={code} />
</div> : null}
{tab == 'profiles' ?
<div>
<div className='group third'>
<h1>{translate('engine profile')}</h1>
<EngineProfile code={code} ship={ship} fuel={fuel} cargo={cargo} pips={pips} boost={boost} />
</div>
<div className='group third'>
<h1>{translate('fsd profile')}</h1>
<FSDProfile code={code} ship={ship} fuel={fuel} cargo={cargo} />
</div>
<div className='group third'>
<h1>{translate('movement profile')}</h1>
<Movement code={code} ship={ship} boost={boost} pips={pips} />
</div>
<div className='group half'>
<h1>{translate('damage to opponent\'s shields')}</h1>
<WeaponDamageChart code={code} ship={ship} opponentDefence={opponent.getShield()} engagementRange={engagementRange} />
</div>
<div className='group half'>
<h1>{translate('damage to opponent\'s hull')}</h1>
<WeaponDamageChart code={code} ship={ship} opponentDefence={opponent.getArmour()} engagementRange={engagementRange} />
</div>
</div> : null}
{tab == 'offence' ?
<div>
<Offence code={code} ship={ship} opponent={opponent} engagementRange={engagementRange} />
</div> : null}
{tab == 'defence' ?
<div>
<Defence code={code} ship={ship} opponent={opponent} engagementRange={engagementRange} />
</div> : null}
</div>
</div>
);
}
}

View File

@@ -0,0 +1,91 @@
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>
);
}
}

175
src/app/components/Pips.jsx Normal file
View File

@@ -0,0 +1,175 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Pip } from './SvgIcons';
import { autoBind } from 'react-extras';
import { Ship } from 'ed-forge';
/**
* Pips displays SYS/ENG/WEP pips and allows users to change them with key presses by clicking on the relevant area.
* Requires an onChange() function of the form onChange(sys, eng, wep) which is triggered whenever the pips change.
*/
export default class Pips extends TranslatedComponent {
static propTypes = {
ship: PropTypes.instanceOf(Ship).isRequired,
pips: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
autoBind(this);
const { ship } = props;
this._incSys = this._change(ship.incSys);
this._incEng = this._change(ship.incEng);
this._incWep = this._change(ship.incWep);
this._reset = this._change(ship.pipsReset);
}
/**
* Add listeners after mounting
*/
componentDidMount() {
document.addEventListener('keydown', this._keyDown);
}
/**
* Remove listeners before unmounting
*/
componentWillUnmount() {
document.removeEventListener('keydown', this._keyDown);
}
/**
* Handle Key Down
* @param {Event} e Keyboard Event
*/
_keyDown(e) {
if (e.ctrlKey || e.metaKey) { // CTRL/CMD
switch (e.keyCode) {
case 37: // Left arrow == increase SYS
e.preventDefault();
this._incSys();
break;
case 38: // Up arrow == increase ENG
e.preventDefault();
this._incEng();
break;
case 39: // Right arrow == increase WEP
e.preventDefault();
this._incWep();
break;
case 40: // Down arrow == reset
e.preventDefault();
this._reset();
break;
}
}
}
/**
* Creates a function that handles pip assignment and call `onChance`.
* @param {String} cb Callback that handles the actual pip assignment
* @param {Boolean} isMc True when increase is by multi crew
* @returns {Function} Function that handles pip assigment
*/
_change(cb, isMc) {
return () => {
cb(isMc);
this.props.onChange(this.props.ship.getDistributorSettingsObject());
};
}
/**
* Set up the rendering for pips
* @returns {Object} Object containing the rendering for the pips
*/
_renderPips() {
const pipsSvg = {
Sys: [],
Eng: [],
Wep: [],
};
for (let k in this.props.pips) {
let { base, mc } = this.props.pips[k];
for (let i = 0; i < Math.floor(base); i++) {
pipsSvg[k].push(<Pip key={i} className='full' />);
}
if (base > Math.floor(base)) {
pipsSvg[k].push(<Pip className='half' key={'half'} />);
}
for (let i = 0; i < mc; i++) {
pipsSvg[k].push(<Pip key={base + i} className='mc' />);
}
for (let i = Math.ceil(base + mc); i < 4; i++) {
pipsSvg[k].push(<Pip className='empty' key={i} />);
}
}
return pipsSvg;
}
/**
* Render pips
* @return {React.Component} contents
*/
render() {
const { ship } = this.props;
const { translate } = this.context.language;
const pipsSvg = this._renderPips();
return (
<span id='pips'>
<table>
<tbody>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td className='clickable' onClick={this._incEng}>{pipsSvg.Eng}</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td className='clickable' onClick={this._incSys}>{pipsSvg.Sys}</td>
<td className='clickable' onClick={this._incEng}>
{translate('ENG')}
</td>
<td className='clickable' onClick={this._incWep}>{pipsSvg.Wep}</td>
</tr>
<tr>
<td>&nbsp;</td>
<td className='clickable' onClick={this._incSys}>
{translate('SYS')}
</td>
<td className='clickable' onClick={this._reset}>
{translate('RST')}
</td>
<td className='clickable' onClick={this._incWep}>
{translate('WEP')}
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td className='clickable' onClick={this._change(ship.incSys, true)}>
<Pip className='mc' />
</td>
<td className='clickable' onClick={this._change(ship.incEng, true)}>
<Pip className='mc' />
</td>
<td className='clickable' onClick={this._change(ship.incWep, true)}>
<Pip className='mc' />
</td>
</tr>
</tbody>
</table>
</span>
);
}
}

View File

@@ -1,29 +1,28 @@
import React from 'react'; import React from 'react';
import d3 from 'd3'; import PropTypes from 'prop-types';
import * as d3 from 'd3';
import cn from 'classnames'; import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import { wrapCtxMenu } from '../utils/UtilityFunctions'; import { wrapCtxMenu } from '../utils/UtilityFunctions';
import { Ship } from 'ed-forge';
import { POWER_METRICS } from 'ed-forge/lib/src/ship-stats';
import autoBind from 'auto-bind';
/** /**
* Round to avoid floating point precision errors * Get the band-class.
* @param {Boolean} selected Band selected * @param {Boolean} selected Band selected
* @param {number} sum Band power sum * @param {Number} relDraw Relative amount of power drawn by this band and
* @param {number} avail Total available power * all prior
* @return {string} CSS Class name * @return {string} CSS Class name
*/ */
function getClass(selected, sum, avail) { function getClass(selected, relDraw) {
return selected ? 'secondary' : ((Math.round(sum * 100) / 100) >= avail) ? 'warning' : 'primary'; if (selected) {
} return 'secondary';
} else if (relDraw >= 1) {
/** return 'warning';
* Get the # label for a Priority band } else {
* @param {number} val Priority Band Watt value return 'primary';
* @param {number} index Priority Band index }
* @param {Function} wattScale Watt Scale function
* @return {number} label / text
*/
function bandText(val, index, wattScale) {
return (val > 0 && wattScale(val) > 13) ? index + 1 : null;
} }
/** /**
@@ -31,12 +30,10 @@ function bandText(val, index, wattScale) {
* Renders the SVG to simulate in-game power bands * Renders the SVG to simulate in-game power bands
*/ */
export default class PowerBands extends TranslatedComponent { export default class PowerBands extends TranslatedComponent {
static propTypes = { static propTypes = {
bands: React.PropTypes.array.isRequired, ship: PropTypes.instanceOf(Ship).isRequired,
available: React.PropTypes.number.isRequired, code: PropTypes.string.isRequired,
width: React.PropTypes.number.isRequired, width: PropTypes.number.isRequired,
code: React.PropTypes.string,
}; };
/** /**
@@ -46,20 +43,16 @@ export default class PowerBands extends TranslatedComponent {
*/ */
constructor(props, context) { constructor(props, context) {
super(props); super(props);
this.wattScale = d3.scale.linear(); autoBind(this);
this.pctScale = d3.scale.linear().domain([0, 1]); this.wattScale = d3.scaleLinear();
this.wattAxis = d3.svg.axis().scale(this.wattScale).outerTickSize(0).orient('top').tickFormat(context.language.formats.r2); this.pctScale = d3.scaleLinear().domain([0, 1]);
this.pctAxis = d3.svg.axis().scale(this.pctScale).outerTickSize(0).orient('bottom').tickFormat(context.language.formats.rPct); this.wattAxis = d3.axisTop(this.wattScale).tickSizeOuter(0).tickFormat(context.language.formats.r2);
this.pctAxis = d3.axisBottom(this.pctScale).tickSizeOuter(0).tickFormat(context.language.formats.rPct);
this._updateDimensions = this._updateDimensions.bind(this);
this._updateScales = this._updateScales.bind(this);
this._selectNone = this._selectNone.bind(this);
this._hidetip = () => this.context.tooltip(); this._hidetip = () => this.context.tooltip();
let maxBand = props.bands[props.bands.length - 1]; this.profile = props.ship.getMetrics(POWER_METRICS);
this.state = { this.state = {
maxPwr: Math.max(props.available, maxBand.retractedSum, maxBand.deployedSum),
ret: {}, ret: {},
dep: {} dep: {}
}; };
@@ -83,8 +76,6 @@ export default class PowerBands extends TranslatedComponent {
let mRight = Math.round(140 * scale); let mRight = Math.round(140 * scale);
let innerWidth = props.width - mLeft - mRight; let innerWidth = props.width - mLeft - mRight;
this._updateScales(innerWidth, this.state.maxPwr, props.available);
this.setState({ this.setState({
barHeight, barHeight,
innerHeight, innerHeight,
@@ -140,41 +131,67 @@ export default class PowerBands extends TranslatedComponent {
this.setState({ dep: Object.assign({}, dep) }); this.setState({ dep: Object.assign({}, dep) });
} }
/**
* Update scale
* @param {number} innerWidth SVG innerwidth
* @param {number} maxPwr Maximum power level MJ (deployed or available)
* @param {number} available Available power MJ
*/
_updateScales(innerWidth, maxPwr, available) {
this.wattScale.range([0, innerWidth]).domain([0, maxPwr]).clamp(true);
this.pctScale.range([0, innerWidth]).domain([0, maxPwr / available]).clamp(true);
}
/** /**
* Update state based on property and context changes * Update state based on property and context changes
* @param {Object} nextProps Incoming/Next properties * @param {Object} nextProps Incoming/Next properties
* @param {Object} nextContext Incoming/Next context * @param {Object} nextContext Incoming/Next context
*/ */
componentWillReceiveProps(nextProps, nextContext) { componentWillReceiveProps(nextProps, nextContext) {
let { innerWidth, maxPwr } = this.state;
let { language, sizeRatio } = this.context; let { language, sizeRatio } = this.context;
let maxBand = nextProps.bands[nextProps.bands.length - 1];
let nextMaxPwr = Math.max(nextProps.available, maxBand.retractedSum, maxBand.deployedSum);
if (language !== nextContext.language) { if (language !== nextContext.language) {
this.wattAxis.tickFormat(nextContext.language.formats.r2); this.wattAxis.tickFormat(nextContext.language.formats.r2);
this.pctAxis.tickFormat(nextContext.language.formats.rPct); this.pctAxis.tickFormat(nextContext.language.formats.rPct);
} }
if (maxPwr != nextMaxPwr) { // Update Axes if max power has changed if (nextProps.width != this.props.width || sizeRatio != nextContext.sizeRatio) {
this._updateScales(innerWidth, nextMaxPwr, nextProps.available);
this.setState({ maxPwr: nextMaxPwr });
} else if (nextProps.width != this.props.width || sizeRatio != nextContext.sizeRatio) {
this._updateDimensions(nextProps, nextContext.sizeRatio); this._updateDimensions(nextProps, nextContext.sizeRatio);
} }
} }
/**
* Assemble bands for relative consumption array.
* @param {Number[]} consumed Array of relative-consumption numbers
* @param {object} selected Object mapping selected bands to 1
* @param {Number} yOffset Offset in y-direction of the bar
* @param {Function} onClick onClick callback
* @returns {React.Component} Bands
*/
_consumedToBands(consumed, selected, yOffset, onClick) {
const { state, wattScale } = this;
const bands = [];
let consumesPrev = 0;
for (let i = 0; i < consumed.length; i++) {
consumesPrev = consumed[i - 1] || consumesPrev;
const consumes = consumed[i];
if (!consumes) {
continue;
}
bands.push(<rect
key={'b' + i}
width={Math.ceil(Math.max(wattScale(consumes - consumesPrev), 0))}
height={state.barHeight}
x={wattScale(consumesPrev)}
y={yOffset + 1}
onClick={onClick.bind(this, i)}
className={getClass(selected[i], consumes)}
/>);
bands.push(<text
key={'t' + i}
dy='0.5em'
textAnchor='middle'
height={state.barHeight}
x={wattScale(consumesPrev) + (wattScale(consumes - consumesPrev) / 2)}
y={yOffset + (state.barHeight / 2)}
onClick={onClick.bind(this, i)}
className='primary-bg'>{i + 1}</text>
);
}
return bands;
}
/** /**
* Render the power bands * Render the power bands
* @return {React.Component} Power bands * @return {React.Component} Power bands
@@ -184,78 +201,27 @@ export default class PowerBands extends TranslatedComponent {
return null; return null;
} }
let { wattScale, pctScale, context, props, state } = this; let { pctScale, context, props, state } = this;
let { translate, formats } = context.language; let { translate, formats } = context.language;
let { f2, pct1, rPct, r2 } = formats; // wattFmt, pctFmt, pctAxis, wattAxis let { f2, pct1 } = formats; // wattFmt, pctFmt
let { available, bands, width } = props; let { ship } = props;
let { innerWidth, maxPwr, ret, dep } = state; let { innerWidth, ret, dep, barHeight } = state;
let pwrWarningClass = cn('threshold', { exceeded: bands[0].retractedSum * 2 >= available });
let deployed = []; let {
let retracted = []; consumed, generated, relativeConsumed, relativeConsumedRetracted
} = ship.getMetrics(POWER_METRICS);
let maxPwr = Math.max(consumed, generated);
let retSum = relativeConsumedRetracted[relativeConsumedRetracted.length - 1];
let depSum = relativeConsumed[relativeConsumed.length - 1];
this.wattScale.range([0, innerWidth]).domain([0, 1]).clamp(true);
this.pctScale.range([0, innerWidth]).domain([0, maxPwr / generated]).clamp(true);
let pwrWarningClass = cn('threshold', { exceeded: retSum > generated * 0.4 });
let retracted = this._consumedToBands(relativeConsumedRetracted, ret, 0, this._selectRet);
let deployed = this._consumedToBands(relativeConsumed, dep, barHeight, this._selectDep);
let retSelected = Object.keys(ret).length > 0; let retSelected = Object.keys(ret).length > 0;
let depSelected = Object.keys(dep).length > 0; let depSelected = Object.keys(dep).length > 0;
let retSum = 0;
let depSum = 0;
for (let i = 0; i < bands.length; i++) {
let b = bands[i];
retSum += (!retSelected || ret[i]) ? b.retracted : 0;
depSum += (!depSelected || dep[i]) ? b.deployed + b.retracted : 0;
if (b.retracted > 0) {
let retLbl = bandText(b.retracted, i, wattScale);
retracted.push(<rect
key={'rB' + i}
width={Math.ceil(Math.max(wattScale(b.retracted), 0))}
height={state.barHeight}
x={Math.floor(Math.max(wattScale(b.retractedSum) - wattScale(b.retracted), 0))}
y={1}
onClick={this._selectRet.bind(this, i)}
className={getClass(ret[i], b.retractedSum, available)}
/>);
if (retLbl) {
retracted.push(<text
key={'rT' + i}
dy='0.5em'
textAnchor='middle'
height={state.barHeight}
x={wattScale(b.retractedSum) - (wattScale(b.retracted) / 2)}
y={state.retY}
onClick={this._selectRet.bind(this, i)}
className='primary-bg'>{retLbl}</text>
);
}
}
if (b.retracted > 0 || b.deployed > 0) {
let depLbl = bandText(b.deployed + b.retracted, i, wattScale);
deployed.push(<rect
key={'dB' + i}
width={Math.ceil(Math.max(wattScale(b.deployed + b.retracted), 0))}
height={state.barHeight}
x={Math.floor(Math.max(wattScale(b.deployedSum) - wattScale(b.retracted) - wattScale(b.deployed), 0))}
y={state.barHeight + 1}
onClick={this._selectDep.bind(this, i)}
className={getClass(dep[i], b.deployedSum, available)}
/>);
if (depLbl) {
deployed.push(<text
key={'dT' + i}
dy='0.5em'
textAnchor='middle'
height={state.barHeight}
x={wattScale(b.deployedSum) - ((wattScale(b.retracted) + wattScale(b.deployed)) / 2)}
y={state.depY}
onClick={this._selectDep.bind(this, i)}
className='primary-bg'>{depLbl}</text>
);
}
}
}
return ( return (
<svg style={{ marginTop: '1em', width: '100%', height: state.height }} onContextMenu={wrapCtxMenu(this._selectNone)}> <svg style={{ marginTop: '1em', width: '100%', height: state.height }} onContextMenu={wrapCtxMenu(this._selectNone)}>
@@ -268,11 +234,11 @@ export default class PowerBands extends TranslatedComponent {
axis.call(this.pctAxis); axis.call(this.pctAxis);
axis.select('g:nth-child(6)').selectAll('line, text').attr('class', pwrWarningClass); axis.select('g:nth-child(6)').selectAll('line, text').attr('class', pwrWarningClass);
}} className='pct axis' transform={`translate(0,${state.innerHeight})`}></g> }} className='pct axis' transform={`translate(0,${state.innerHeight})`}></g>
<line x1={pctScale(0.5)} x2={pctScale(0.5)} y1='0' y2={state.innerHeight} className={pwrWarningClass} /> <line x1={pctScale(0.4)} x2={pctScale(0.4)} y1='0' y2={state.innerHeight} className={pwrWarningClass} />
<text dy='0.5em' x='-3' y={state.retY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'retracted')} onMouseLeave={this._hidetip}>{translate('ret')}</text> <text dy='0.5em' x='-3' y={state.retY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'retracted')} onMouseLeave={this._hidetip}>{translate('ret')}</text>
<text dy='0.5em' x='-3' y={state.depY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'deployed', { orientation: 's', cap: 1 })} onMouseLeave={this._hidetip}>{translate('dep')}</text> <text dy='0.5em' x='-3' y={state.depY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'deployed', { orientation: 's', cap: 1 })} onMouseLeave={this._hidetip}>{translate('dep')}</text>
<text dy='0.5em' x={innerWidth + 5} y={state.retY} className={getClass(retSelected, retSum, available)}>{f2(Math.max(0, retSum))} ({pct1(Math.max(0, retSum / available))})</text> <text dy='0.5em' x={innerWidth + 5} y={state.retY} className={getClass(retSelected, retSum, generated)}>{f2(Math.max(0, retSum * generated))} ({pct1(Math.max(0, retSum))})</text>
<text dy='0.5em' x={innerWidth + 5} y={state.depY} className={getClass(depSelected, depSum, available)}>{f2(Math.max(0, depSum))} ({pct1(Math.max(0, depSum / available))})</text> <text dy='0.5em' x={innerWidth + 5} y={state.depY} className={getClass(depSelected, depSum, generated)}>{f2(Math.max(0, depSum * generated))} ({pct1(Math.max(0, depSum))})</text>
</g> </g>
</svg> </svg>
); );

View File

@@ -1,26 +1,51 @@
import React from 'react'; import React from 'react';
import { findDOMNode } from 'react-dom'; import PropTypes from 'prop-types';
import cn from 'classnames'; import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import PowerBands from './PowerBands'; import PowerBands from './PowerBands';
import { slotName, slotComparator } from '../utils/SlotFunctions';
import { Power, NoPower } from './SvgIcons'; import { Power, NoPower } from './SvgIcons';
import autoBind from 'auto-bind';
import { Ship, Module } from 'ed-forge';
const POWER = [ /**
null, * Makes a comparison based on the order `false < undefined < true` (fut) and
null, * maps it to `[-1, 0, 1]`.
<NoPower className='icon warning' />, * @param {boolean} a Bool or undefined
<Power className='secondary-disabled' /> * @param {boolean} b Bool or undefined
]; * @returns {number} Comparison
*/
function futComp(a, b) {
switch (a) {
case false: return (b === false ? 0 : 1);
// The next else-expression maps false to -1 and true to 1
case undefined: return (b === undefined ? 0 : 2 * Number(b) - 1);
case true: return (b === true ? 0 : -1);
}
}
/**
* Get the enabled-icon.
* @param {boolean} enabled Is the module enabled?
* @returns {React.Component} Enabled icon.
*/
function getPowerIcon(enabled) {
if (enabled === undefined) {
return null;
}
if (enabled) {
return <Power className='secondary-disabled' />;
} else {
return <NoPower className='icon warning' />;
}
}
/** /**
* Power Management Section * Power Management Section
*/ */
export default class PowerManagement extends TranslatedComponent { export default class PowerManagement extends TranslatedComponent {
static PropTypes = { static propTypes = {
ship: React.PropTypes.object.isRequired, ship: PropTypes.instanceOf(Ship).isRequired,
code: React.PropTypes.string.isRequired, code: PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired
}; };
/** /**
@@ -29,19 +54,17 @@ export default class PowerManagement extends TranslatedComponent {
*/ */
constructor(props) { constructor(props) {
super(props); super(props);
this._renderPowerRows = this._renderPowerRows.bind(this); autoBind(this);
this._updateWidth = this._updateWidth.bind(this);
this._sort = this._sort.bind(this);
this.state = { this.state = {
predicate: 'n', predicate: 'pwr',
desc: true, desc: true,
width: 0 width: 0
}; };
} }
/** /**
* Set the sort order and sort * Set the sort order
* @param {string} predicate Sort predicate * @param {string} predicate Sort predicate
*/ */
_sortOrder(predicate) { _sortOrder(predicate) {
@@ -53,50 +76,51 @@ export default class PowerManagement extends TranslatedComponent {
desc = true; desc = true;
} }
this._sort(this.props.ship, predicate, desc);
this.setState({ predicate, desc }); this.setState({ predicate, desc });
} }
/** /**
* Sorts the power list * Sorts the power list
* @param {Ship} ship Ship instance * @param {Module[]} modules Modules to sort
* @param {string} predicate Sort predicate * @returns {Module[]} Sorted modules
* @param {Boolean} desc Sort order descending
*/ */
_sort(ship, predicate, desc) { _sortAndFilter(modules) {
let powerList = ship.powerList; modules = modules.filter((m) => m.get('powerdraw') >= 0);
let comp = slotComparator.bind(null, this.context.language.translate); let { translate } = this.context.language;
const { predicate, desc } = this.state;
let comp;
switch (predicate) { switch (predicate) {
case 'n': comp = comp(null, desc); break; case 'n': comp = (a, b) => translate(a.readMeta('type')).localeCompare(
case 't': comp = comp((a, b) => a.type.localeCompare(b.type), desc); break; translate(b.readMeta('type'))
case 'pri': comp = comp((a, b) => a.priority - b.priority, desc); break; ); break;
case 'pwr': comp = comp((a, b) => a.m.power - b.m.power, desc); break; // case 't': comp = comp((a, b) => a.type.localeCompare(b.type), desc); break;
case 'r': comp = comp((a, b) => ship.getSlotStatus(a) - ship.getSlotStatus(b), desc); break; case 'pri': comp = (a, b) => a.getPowerPriority() - b.getPowerPriority(); break;
case 'd': comp = comp((a, b) => ship.getSlotStatus(a, true) - ship.getSlotStatus(b, true), desc); break; case 'pwr': comp = (a, b) => a.get('powerdraw') - b.get('powerdraw'); break;
case 'r': comp = (a, b) => futComp(a.isPowered().retracted, b.isPowered().retracted); break;
case 'd': comp = (a, b) => futComp(a.isPowered().deployed, b.isPowered().deployed); break;
} }
modules.sort(comp);
powerList.sort(comp); if (desc) {
modules.reverse();
}
return modules;
} }
/** /**
* Update slot priority * Creates a callback that changes the power priority for the given module
* @param {Object} slot Slot model * based on the given delta.
* @param {number} inc increment / decrement * @param {Module} m Module to set the priority for
* @param {Number} delta Delta to set
* @returns {Function} Callback
*/ */
_priority(slot, inc) { _prioCb(m, delta) {
if (this.props.ship.setSlotPriority(slot, slot.priority + inc)) { return () => {
this.props.onChange(); const prio = m.getPowerPriority();
} const newPrio = Math.max(0, prio + delta);
} if (0 <= newPrio) {
m.setPowerPriority(newPrio);
/** }
* Toggle slot active/inactive };
* @param {Object} slot Slot model
*/
_toggleEnabled(slot) {
this.props.ship.setSlotEnabled(slot, !slot.enabled);
this.props.onChange();
} }
/** /**
@@ -110,36 +134,35 @@ export default class PowerManagement extends TranslatedComponent {
_renderPowerRows(ship, translate, pwr, pct) { _renderPowerRows(ship, translate, pwr, pct) {
let powerRows = []; let powerRows = [];
for (let i = 0, l = ship.powerList.length; i < l; i++) { let modules = this._sortAndFilter(ship.getModules());
let slot = ship.powerList[i]; for (let m of modules) {
let retractedElem = null, deployedElem = null;
if (slot.m && slot.m.power) { const flipEnabled = () => m.setEnabled();
let m = slot.m; if (m.isEnabled()) {
let toggleEnabled = this._toggleEnabled.bind(this, slot); let powered = m.isPowered();
let retractedElem = null, deployedElem = null; retractedElem = <td className='ptr upp' onClick={flipEnabled}>{getPowerIcon(powered.retracted)}</td>;
deployedElem = <td className='ptr upp' onClick={flipEnabled}>{getPowerIcon(powered.deployed)}</td>;
if (slot.enabled) { } else {
retractedElem = <td className='ptr upp' onClick={toggleEnabled}>{POWER[ship.getSlotStatus(slot, false)]}</td>; retractedElem = <td className='ptr disabled upp' colSpan='2' onClick={flipEnabled}>{translate('disabled')}</td>;
deployedElem = <td className='ptr upp' onClick={toggleEnabled}>{POWER[ship.getSlotStatus(slot, true)]}</td>;
} else {
retractedElem = <td className='ptr disabled upp' colSpan='2' onClick={toggleEnabled}>{translate('disabled')}</td>;
}
powerRows.push(<tr key={i} className={cn('highlight', { disabled: !slot.enabled })}>
<td className='ptr' style={{ width: '1em' }} onClick={toggleEnabled}>{m.class + m.rating}</td>
<td className='ptr le shorten cap' onClick={toggleEnabled}>{slotName(translate, slot)}</td>
<td className='ptr' onClick={toggleEnabled}><u>{translate(slot.type)}</u></td>
<td>
<span className='flip ptr btn' onClick={this._priority.bind(this, slot, -1)}>&#9658;</span>
{' ' + (slot.priority + 1) + ' '}
<span className='ptr btn' onClick={this._priority.bind(this, slot, 1)}>&#9658;</span>
</td>
<td className='ri ptr' style={{ width: '3.25em' }} onClick={toggleEnabled}>{pwr(m.power)}</td>
<td className='ri ptr' style={{ width: '3em' }} onClick={toggleEnabled}><u>{pct(m.power / ship.powerAvailable)}</u></td>
{retractedElem}
{deployedElem}
</tr>);
} }
const slot = m.getSlot();
powerRows.push(<tr key={slot} className={cn('highlight', { disabled: !m.isEnabled() })}>
<td className='ptr' style={{ width: '1em' }} onClick={flipEnabled}>{String(m.getClass()) + m.getRating()}</td>
<td className='ptr le shorten cap' onClick={flipEnabled}>{translate(m.readMeta('type'))}</td>
{/* <td className='ptr' onClick={flipEnabled}><u>{translate(slot.type)}</u></td> */}
<td>
<span className='flip ptr btn' onClick={this._prioCb(m, -1)}>&#9658;</span>
{' ' + (m.getPowerPriority() + 1) + ' '}
<span className='ptr btn' onClick={this._prioCb(m, 1)}>&#9658;</span>
</td>
<td className='ri ptr' style={{ width: '3.25em' }} onClick={flipEnabled}>{pwr(m.get('powerdraw'))}</td>
<td className='ri ptr' style={{ width: '3em' }} onClick={flipEnabled}>
<u>{pct(m.get('powerdraw') / ship.getPowerPlant().get('powercapacity'))}</u>
</td>
{retractedElem}
{deployedElem}
</tr>);
} }
return powerRows; return powerRows;
} }
@@ -148,14 +171,13 @@ export default class PowerManagement extends TranslatedComponent {
* Update power bands width from DOM * Update power bands width from DOM
*/ */
_updateWidth() { _updateWidth() {
this.setState({ width: findDOMNode(this).offsetWidth }); this.setState({ width: this.node.offsetWidth });
} }
/** /**
* Add listeners when about to mount and sort power list * Add listeners when about to mount and sort power list
*/ */
componentWillMount() { componentWillMount() {
this._sort(this.props.ship, this.state.predicate, this.state.desc);
this.resizeListener = this.context.onWindowResize(this._updateWidth); this.resizeListener = this.context.onWindowResize(this._updateWidth);
} }
@@ -166,17 +188,6 @@ export default class PowerManagement extends TranslatedComponent {
this._updateWidth(); this._updateWidth();
} }
/**
* Sort power list if the ship instance has changed
* @param {Object} nextProps Incoming/Next properties
* @param {Object} nextState Incoming/Next state
*/
componentWillUpdate(nextProps, nextState) {
if (this.props.ship != nextProps.ship) {
this._sort(nextProps.ship, nextState.predicate, nextState.desc);
}
}
/** /**
* Remove listeners on unmount * Remove listeners on unmount
*/ */
@@ -191,39 +202,38 @@ export default class PowerManagement extends TranslatedComponent {
render() { render() {
let { ship, code } = this.props; let { ship, code } = this.props;
let { translate, formats } = this.context.language; let { translate, formats } = this.context.language;
let pwr = formats.f2; let pp = ship.getPowerPlant();
let pp = ship.standard[0].m;
let sortOrder = this._sortOrder;
return ( return (
<div className='group half' id='componentPriority'> <div ref={node => this.node = node} className='group half' id='componentPriority'>
<table style={{ width: '100%' }}> <table style={{ width: '100%' }}>
<thead> <thead>
<tr className='main'> <tr className='main'>
<th colSpan='2' className='sortable le' onClick={sortOrder.bind(this, 'n')} >{translate('module')}</th> <th colSpan='2' className='sortable le' onClick={() => this._sortOrder('n')} >{translate('module')}</th>
<th style={{ width: '3em' }} className='sortable' onClick={sortOrder.bind(this, 't')} >{translate('type')}</th> {/* <th style={{ width: '3em' }} className='sortable' onClick={() => this._sortOrder('t')} >{translate('type')}</th> */}
<th style={{ width: '4em' }} className='sortable' onClick={sortOrder.bind(this, 'pri')} >{translate('pri')}</th> <th style={{ width: '4em' }} className='sortable' onClick={() => this._sortOrder('pri')} >{translate('pri')}</th>
<th colSpan='2' className='sortable' onClick={sortOrder.bind(this, 'pwr')} >{translate('PWR')}</th> <th colSpan='2' className='sortable' onClick={() => this._sortOrder('pwr')} >{translate('PWR')}</th>
<th style={{ width: '3em' }} className='sortable' onClick={sortOrder.bind(this, 'r')} >{translate('ret')}</th> <th style={{ width: '3em' }} className='sortable' onClick={() => this._sortOrder('r')} >{translate('ret')}</th>
<th style={{ width: '3em' }} className='sortable' onClick={sortOrder.bind(this, 'd')} >{translate('dep')}</th> <th style={{ width: '3em' }} className='sortable' onClick={() => this._sortOrder('d')} >{translate('dep')}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td>{pp.class + pp.rating}</td> <td>{String(pp.getClass()) + pp.getRating()}</td>
<td className='le shorten cap' >{translate('pp')}</td> <td className='le shorten cap' >{translate('pp')}</td>
<td><u >{translate('SYS')}</u></td>
<td>1</td> <td>1</td>
<td className='ri'>{pwr(pp.pGen)}</td> <td className='ri'>{formats.f2(pp.get('powercapacity'))}</td>
<td className='ri'><u>100%</u></td> <td className='ri'><u>100%</u></td>
<td></td> <td></td>
<td></td> <td></td>
</tr> </tr>
<tr><td style={{ lineHeight:0 }} colSpan='8'><hr style={{ margin: '0 0 3px', background: '#ff8c0d', border: 0, height: 1 }} /></td></tr> <tr><td style={{ lineHeight:0 }} colSpan='8'>
{this._renderPowerRows(ship, translate, pwr, formats.pct1)} <hr style={{ margin: '0 0 3px', background: '#ff8c0d', border: 0, height: 1 }} />
</td></tr>
{this._renderPowerRows(ship, translate, formats.f2, formats.pct1)}
</tbody> </tbody>
</table> </table>
<PowerBands width={this.state.width} code={code} available={ship.standard[0].m.pGen} bands={ship.priorityBands} /> <PowerBands width={this.state.width} ship={ship} code={code} />
</div> </div>
); );
} }

View File

@@ -0,0 +1,147 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Rocket } from './SvgIcons';
import Persist from '../stores/Persist';
import cn from 'classnames';
import { Factory, Ship } from 'ed-forge';
import autoBind from 'auto-bind';
import { isEqual } from 'lodash';
/**
* Ship picker
* Requires an onChange() function of the form onChange(ship), providing the ship, which is triggered on ship change
*/
export default class ShipPicker extends TranslatedComponent {
static propTypes = {
onChange: PropTypes.func.isRequired,
ship: PropTypes.instanceOf(Ship).isRequired,
};
/**
* constructor
* @param {object} props Properties react
* @param {object} context react context
*/
constructor(props, context) {
super(props);
autoBind(this);
this.state = {
menuOpen: false,
opponent: {
self: true,
type: props.ship.getShipType(),
stock: false,
id: undefined,
},
};
}
/**
* Update ship
* @param {boolean} self True to compare with ship itself
* @param {object} type The ship type
* @param {boolean} stock True to compare with a stock version of given type
* @param {string} id The build's stored ID
*/
_shipChange(self, type, stock = false, id = null) {
const opponent = { self, type, stock, id };
if (isEqual(opponent, this.state.opponent)) {
this.setState({ menuOpen: false });
} else {
const { onChange } = this.props;
if (self) {
onChange(this.props.ship);
} else if (stock) {
onChange(Factory.newShip(type));
} else {
onChange(new Ship(Persist.getBuild(type, id)));
}
this.setState({ menuOpen: false, opponent });
}
}
/**
* Render the menu for the picker
* @returns {object} the picker menu
*/
_renderPickerMenu() {
const { menuOpen } = this.state;
if (!menuOpen) {
return null;
}
const { translate } = this.context.language;
const { self, type, stock, id } = this.state.opponent;
return <div className='menu-list' onClick={(e) => e.stopPropagation()}>
<div className='quad'>
{Factory.getAllShipTypes().sort().map((shipType) =>
<ul key={shipType} className='block'>
{translate(shipType)}
{/* Add stock build */}
<li key={shipType}
onClick={this._shipChange.bind(this, false, shipType, true)}
className={cn({ selected: stock && type === shipType })}>
{translate('stock')}
</li>
{Persist.getBuildsNamesFor(shipType).sort().map((storedId) =>
<li key={`${shipType}-${storedId}`}
onClick={this._shipChange.bind(this, false, shipType, false, storedId)}
className={ cn({ selected: type === shipType && id === storedId })}>
{storedId}
</li>)}
{/* Add ship itself */}
{(this.props.ship.getShipType() === shipType ?
<li key='self'
onClick={this._shipChange.bind(this, true, shipType)}
className={cn({ selected: self })}>
{translate('THIS_SHIP')}
</li> :
null)}
</ul>)}
</div>
</div>;
}
/**
* Toggle the menu state
*/
_toggleMenu() {
const { menuOpen } = this.state;
this.setState({ menuOpen: !menuOpen });
}
/**
* Render picker
* @return {React.Component} contents
*/
render() {
const { translate } = this.context.language;
const { ship } = this.props;
const { menuOpen } = this.state;
const { self, type, stock, id } = this.state.opponent;
let label;
if (self) {
label = translate('THIS_SHIP');
} else if (stock) {
label = translate('stock');
} else {
label = id;
}
return (
<div className='shippicker' onClick={ (e) => e.stopPropagation() }>
<div className='menu'>
<div className={cn('menu-header', { selected: menuOpen })} onClick={this._toggleMenu}>
<span><Rocket className='warning' /></span>
<span className='menu-item-label'>
{`${translate(type)}: ${label}`}
</span>
</div>
{this._renderPickerMenu()}
</div>
</div>
);
}
}

View File

@@ -1,107 +1,267 @@
import autoBind from 'auto-bind';
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames'; import cn from 'classnames';
import { SizeMap } from '../shipyard/Constants';
import { Warning } from './SvgIcons'; import { Warning } from './SvgIcons';
import { ShipProps } from 'ed-forge';
import { BOOST_INTERVAL, MINIMUM_MASS } from 'ed-forge/lib/src/ship-stats';
const {
SPEED, BOOST_SPEED, DAMAGE_METRICS, JUMP_METRICS, SHIELD_METRICS,
ARMOUR_METRICS, CARGO_CAPACITY, FUEL_CAPACITY, UNLADEN_MASS, LADEN_MASS,
MODULE_PROTECTION_METRICS, PASSENGER_CAPACITY
} = ShipProps;
/** /**
* Ship Summary Table / Stats * Ship Summary Table / Stats
*/ */
export default class ShipSummaryTable extends TranslatedComponent { export default class ShipSummaryTable extends TranslatedComponent {
static propTypes = { static propTypes = {
ship: React.PropTypes.object.isRequired ship: PropTypes.object.isRequired,
code: PropTypes.string.isRequired,
}; };
/**
* The ShipSummaryTable constructor
* @param {Object} props The props
*/
constructor(props) {
super(props);
autoBind(this);
this.state = {
shieldColour: 'blue'
};
}
/** /**
* Render the table * Render the table
* @return {React.Component} Summary table * @return {React.Component} Summary table
*/ */
render() { render() {
let ship = this.props.ship; const { ship } = this.props;
let { language, tooltip, termtip } = this.context; let { language, tooltip, termtip } = this.context;
let translate = language.translate; let translate = language.translate;
let u = language.units; let u = language.units;
let formats = language.formats; let formats = language.formats;
let round = formats.round; let { time, int, f1, f2 } = formats;
let { time, int } = formats;
let armourDetails = null;
let sgClassNames = cn({ warning: ship.sgSlot && !ship.shieldStrength, muted: !ship.sgSlot });
let sgRecover = '-';
let sgRecharge = '-';
let hide = tooltip.bind(null, null); let hide = tooltip.bind(null, null);
if (ship.armourMultiplier > 1 || ship.armourAdded) { const speed = ship.get(SPEED);
armourDetails = <u>({ const shipBoost = ship.get(BOOST_SPEED);
(ship.armourMultiplier > 1 ? formats.rPct(ship.armourMultiplier) : '') + const boostInterval = ship.get(BOOST_INTERVAL);
(ship.armourAdded ? ' + ' + ship.armourAdded : '') const canThrust = 0 < speed;
})</u>; const canBoost = canThrust && !isNaN(shipBoost);
} const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
if (ship.shieldStrength) { const sgMetrics = ship.get(SHIELD_METRICS);
sgRecover = time(ship.calcShieldRecovery()); const armourMetrics = ship.get(ARMOUR_METRICS);
sgRecharge = time(ship.calcShieldRecharge()); const damageMetrics = ship.get(DAMAGE_METRICS);
const moduleProtectionMetrics = ship.get(MODULE_PROTECTION_METRICS);
const timeToDrain = damageMetrics.timeToDrain;
const shieldGenerator = ship.getShieldGenerator();
const sgClassNames = cn({
warning: shieldGenerator && !shieldGenerator.isEnabled(),
muted: !shieldGenerator,
});
const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
const sgType = shieldGenerator ? shieldGenerator.readMeta('type') : undefined;
let shieldColour = 'blue';
switch (sgType) {
case 'biweaveshieldgen': shieldColour = 'purple'; break;
case 'prismaticshieldgen': shieldColour = 'green'; break;
} }
this.state = { shieldColour };
const jumpRangeMetrics = ship.getMetrics(JUMP_METRICS);
return <div id='summary'> return <div id='summary'>
<table id='summaryTable'> <div style={{ display: 'table', width: '100%' }}>
<thead> <div style={{ display: 'table-row' }}>
<tr className='main'> <table className={'summaryTable'}>
<th rowSpan={2}>{translate('size')}</th> <thead>
<th onMouseEnter={termtip.bind(null, 'maneuverability')} onMouseLeave={hide} rowSpan={2}>{translate('MNV')}</th> <tr className='main'>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canThrust() }) }>{translate('speed')}</th> <th rowSpan={2} className={ cn({ 'bg-warning-disabled': speed == 0 }) }>{translate('speed')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canBoost() }) }>{translate('boost')}</th> <th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
<th onMouseEnter={termtip.bind(null, 'damage per second')} onMouseLeave={hide} rowSpan={2}>{translate('DPS')}</th> <th colSpan={5} className={ cn({ 'bg-warning-disabled': jumpRangeMetrics.jumpRangeCurrent == 0 }) }>{translate('jump range')}</th>
<th rowSpan={2}>{translate('armour')}</th> <th rowSpan={2}>{translate('shield')}</th>
<th colSpan={3}>{translate('shields')}</th> <th rowSpan={2}>{translate('integrity')}</th>
<th colSpan={3}>{translate('mass')}</th> <th rowSpan={2}>{translate('DPS')}</th>
<th rowSpan={2}>{translate('cargo')}</th> <th rowSpan={2}>{translate('EPS')}</th>
<th rowSpan={2}>{translate('fuel')}</th> <th rowSpan={2}>{translate('TTD')}</th>
<th colSpan={3}>{translate('jump range')}</th> {/* <th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th> */}
<th onMouseEnter={termtip.bind(null, 'PHRASE_FASTEST_RANGE')} onMouseLeave={hide} colSpan={3}>{translate('fastest range')}</th> <th rowSpan={2}>{translate('cargo')}</th>
<th onMouseEnter={termtip.bind(null, 'mass lock factor')} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th> <th rowSpan={2} onMouseEnter={termtip.bind(null, 'passenger capacity', { cap: 0 })} onMouseLeave={hide}>{translate('pax')}</th>
</tr> <th rowSpan={2}>{translate('fuel')}</th>
<tr> <th colSpan={3}>{translate('mass')}</th>
<th className='lft'>{translate('strength')}</th> <th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECOVER', { cap: 0 })} onMouseLeave={hide}>{translate('recovery')}</th> <th rowSpan={2}>{translate('crew')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECHARGE', { cap: 0 })} onMouseLeave={hide}>{translate('recharge')}</th> <th onMouseEnter={termtip.bind(null, 'mass lock factor', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
<th className='lft'>{translate('hull')}</th> <th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_BOOST_INTERVAL', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('boost interval')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_UNLADEN', { cap: 0 })} onMouseLeave={hide}>{translate('unladen')}</th> {/* TODO: Resting heat */}
<th onMouseEnter={termtip.bind(null, 'PHRASE_LADEN', { cap: 0 })} onMouseLeave={hide}>{translate('laden')}</th> {/* <th rowSpan={2}>{translate('resting heat (Beta)')}</th> */}
<th className='lft'>{translate('max')}</th> </tr>
<th>{translate('full tank')}</th> <tr>
<th>{translate('laden')}</th> <th className="lft">{translate('max')}</th>
<th className='lft'>{translate('jumps')}</th> <th>{translate('unladen')}</th>
<th>{translate('unladen')}</th> <th>{translate('laden')}</th>
<th>{translate('laden')}</th> <th>{translate('total unladen')}</th>
</tr> <th>{translate('total laden')}</th>
</thead> <th className='lft'>{translate('hull')}</th>
<tbody> <th>{translate('unladen')}</th>
<tr> <th>{translate('laden')}</th>
<td className='cap'>{translate(SizeMap[ship.class])}</td> </tr>
<td>{ship.agility}/10</td> </thead>
<td>{ ship.canThrust() ? <span>{int(ship.topSpeed)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td> <tbody>
<td>{ ship.canBoost() ? <span>{int(ship.topBoost)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td> <tr>
<td>{round(ship.totalDps)}</td> <td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })}
<td>{int(ship.armour)} {armourDetails}</td> onMouseLeave={hide}
<td className={sgClassNames}>{int(ship.shieldStrength)} {u.MJ} { ship.shieldMultiplier > 1 && ship.shieldStrength > 0 ? <u>({formats.rPct(ship.shieldMultiplier)})</u> : null }</td> >{canThrust ?
<td className={sgClassNames}>{sgRecover}</td> <span>{int(speed)}{u['m/s']}</span> :
<td className={sgClassNames}>{sgRecharge}</td> <span className='warning'>0<Warning/></span>
<td>{ship.hullMass} {u.T}</td> }</td>
<td>{round(ship.unladenMass)} {u.T}</td> <td onMouseEnter={termtip.bind(null, boostTooltip, { cap: 0 })}
<td>{round(ship.ladenMass)} {u.T}</td> onMouseLeave={hide}
<td>{round(ship.cargoCapacity)} {u.T}</td> >{canBoost ?
<td>{round(ship.fuelCapacity)} {u.T}</td> <span>{int(shipBoost)}{u['m/s']}</span> :
<td>{round(ship.unladenRange)} {u.LY}</td> <span className='warning'>0<Warning/></span>
<td>{round(ship.fullTankRange)} {u.LY}</td> }</td>
<td>{round(ship.ladenRange)} {u.LY}</td> <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })}
<td>{round(ship.maxJumpCount)}</td> onMouseLeave={hide}
<td>{round(ship.unladenFastestRange)} {u.LY}</td> >{<span>{f2(jumpRangeMetrics.jumpRangeMax)}{u.LY}</span>}</td>
<td>{round(ship.ladenFastestRange)} {u.LY}</td> <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })}
<td>{ship.masslock}</td> onMouseLeave={hide}
</tr> >{<span>{f2(jumpRangeMetrics.jumpRangeUnladen)}{u.LY}</span>}</td>
</tbody> <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })}
</table> onMouseLeave={hide}
>{<span>{f2(jumpRangeMetrics.jumpRangeLaden)}{u.LY}</span>}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })}
onMouseLeave={hide}
>{<span>{f2(jumpRangeMetrics.totalRangeUnladen)}{u.LY}</span>}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })}
onMouseLeave={hide}
>{<span>{f2(jumpRangeMetrics.totalRangeLaden)}{u.LY}</span>}</td>
<td className={sgClassNames}
onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })}
onMouseLeave={hide}
>{int(sgMetrics.shieldStrength)}{u.MJ}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })}
onMouseLeave={hide}
>{int(armourMetrics.armour)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })}
onMouseLeave={hide}
>{f1(damageMetrics.dps)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })}
onMouseLeave={hide}
>{f1(damageMetrics.eps)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })}
onMouseLeave={hide}
>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
{/* <td>{f1(ship.totalHps)}</td> */}
<td>{ship.get(CARGO_CAPACITY)}{u.T}</td>
<td>{ship.get(PASSENGER_CAPACITY)}</td>
<td>{ship.get(FUEL_CAPACITY)}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })}
onMouseLeave={hide}
>{ship.readProp('hullmass')}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })}
onMouseLeave={hide}
>{int(ship.get(MINIMUM_MASS))}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })}
onMouseLeave={hide}
>{int(ship.get(LADEN_MASS))}{u.T}</td>
<td>{int(ship.readProp('hardness'))}</td>
<td>{ship.readMeta('crew')}</td>
<td>{ship.readProp('masslock')}</td>
<td>{time(boostInterval)}</td>
{/* TODO: resting heat */}
{/* <td>{NaN}</td> */}
</tr>
</tbody>
</table>
<table className={'summaryTable'}>
<thead className={this.state.shieldColour}>
<tr>
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'shield', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('shield')}</th>
<th colSpan={4} className='lft'>{translate('resistance')}</th>
<th colSpan={5} onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('HP')}`}</th>
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECOVER', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('recovery')}</th>
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECHARGE', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('recharge')}</th>
</tr>
<tr>
<th>{`${translate('explosive')}`}</th>
<th>{`${translate('kinetic')}`}</th>
<th>{`${translate('thermal')}`}</th>
<th></th>
<th className={'bordered'}>{`${translate('absolute')}`}</th>
<th>{`${translate('explosive')}`}</th>
<th>{`${translate('kinetic')}`}</th>
<th>{`${translate('thermal')}`}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>{translate(sgType || 'No Shield')}</td>
<td>{formats.pct1(1 - sgMetrics.explosive.damageMultiplier)}</td>
<td>{formats.pct1(1 - sgMetrics.kinetic.damageMultiplier)}</td>
<td>{formats.pct1(1 - sgMetrics.thermal.damageMultiplier)}</td>
<td></td>
<td>{int(sgMetrics.shieldStrength || 0)}{u.MJ}</td>
<td>{int(sgMetrics.shieldStrength / sgMetrics.explosive.damageMultiplier || 0)}{u.MJ}</td>
<td>{int(sgMetrics.shieldStrength / sgMetrics.kinetic.damageMultiplier || 0)}{u.MJ}</td>
<td>{int(sgMetrics.shieldStrength / sgMetrics.thermal.damageMultiplier || 0)}{u.MJ}</td>
<td></td>
<td>{isNaN(sgMetrics.recover) ? translate('Never') : formats.time(sgMetrics.recover)}</td>
<td>{isNaN(sgMetrics.recharge) ? translate('Never') : formats.time(sgMetrics.recharge)}</td>
</tr>
</tbody>
<thead>
<tr>
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'armour', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('armour')}</th>
<th colSpan={4} className='lft'>{translate('resistance')}</th>
<th colSpan={5} onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('HP')}`}</th>
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'TT_MODULE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('raw module armour')}</th>
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'TT_MODULE_PROTECTION_INTERNAL', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('internal protection')}</th>
</tr>
<tr>
<th>{`${translate('explosive')}`}</th>
<th>{`${translate('kinetic')}`}</th>
<th>{`${translate('thermal')}`}</th>
<th>{`${translate('caustic')}`}</th>
<th className={'bordered'}>{`${translate('absolute')}`}</th>
<th>{`${translate('explosive')}`}</th>
<th>{`${translate('kinetic')}`}</th>
<th>{`${translate('thermal')}`}</th>
<th>{`${translate('caustic')}`}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{translate(ship.getAlloys().readMeta('type') || 'No Armour')}</td>
<td>{formats.pct1(1 - armourMetrics.explosive.damageMultiplier)}</td>
<td>{formats.pct1(1 - armourMetrics.kinetic.damageMultiplier)}</td>
<td>{formats.pct1(1 - armourMetrics.thermal.damageMultiplier)}</td>
<td>{formats.pct1(1 - armourMetrics.caustic.damageMultiplier)}</td>
<td>{int(armourMetrics.armour)}</td>
<td>{int(armourMetrics.armour / armourMetrics.explosive.damageMultiplier)}</td>
<td>{int(armourMetrics.armour / armourMetrics.kinetic.damageMultiplier)}</td>
<td>{int(armourMetrics.armour / armourMetrics.thermal.damageMultiplier)}</td>
<td>{int(armourMetrics.armour / armourMetrics.caustic.damageMultiplier)}</td>
<td>{int(moduleProtectionMetrics.moduleArmour)}</td>
<td>{formats.pct1(1 - moduleProtectionMetrics.moduleProtection)}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>; </div>;
} }
} }

View File

@@ -1,166 +1,160 @@
import React from 'react'; import React from 'react';
import { findDOMNode } from 'react-dom'; import PropTypes from 'prop-types';
import autoBind from 'auto-bind';
const MARGIN_LR = 8; // Left/ Right margin
const MARGIN_LR = 8; // Left/ Right margin
/**
* Horizontal Slider /**
*/ * Horizontal Slider
export default class Slider extends React.Component { */
export default class Slider extends React.Component {
static defaultProps = { static defaultProps = {
axis: false, axis: false,
min: 0, min: 0,
max: 1, max: 1,
scale: 1 // SVG render scale scale: 1 // SVG render scale
}; };
static propTypes = { static propTypes = {
axis: React.PropTypes.bool, axis: PropTypes.bool,
axisUnit: React.PropTypes.string, axisUnit: PropTypes.string,// units (T, M, etc.)
max: React.PropTypes.number, max: PropTypes.number,
min: React.PropTypes.number, min: PropTypes.number,
onChange: React.PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,// function which determins percent value
onResize: React.PropTypes.func, onResize: PropTypes.func,
percent: React.PropTypes.number.isRequired, percent: PropTypes.number.isRequired,// value of slider
scale: React.PropTypes.number scale: PropTypes.number
}; };
/** /**
* Constructor * Constructor
* @param {Object} props React Component properties * @param {Object} props React Component properties
*/ */
constructor(props) { constructor(props) {
super(props); super(props);
this._down = this._down.bind(this); autoBind(this);
this._move = this._move.bind(this);
this._up = this._up.bind(this); this.state = { width: 0 };
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) {
* On Mouse/Touch down handler let rect = event.currentTarget.getBoundingClientRect();
* @param {SyntheticEvent} event Event this.left = rect.left;
*/ this.width = rect.width;
_down(event) { this._move(event);
let rect = event.currentTarget.getBoundingClientRect(); }
this.left = rect.left;
this.width = rect.width; /**
this._move(event); * Update the slider percentage on move
} * @param {SyntheticEvent} event Event
*/
/** _move(event) {
* Update the slider percentage on move if(this.width !== null && this.left != null) {
* @param {SyntheticEvent} event Event let clientX = event.touches ? event.touches[0].clientX : event.clientX;
*/ event.preventDefault();
_move(event) { this._updatePercent(clientX - this.left, this.width);
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) {
* On Mouse/Touch up handler event.preventDefault();
* @param {Event} event DOM Event this.left = null;
*/ this.width = null;
_up(event) { }
event.preventDefault();
this.left = null; /**
this.width = null; * Determine if the user is still dragging
} * @param {SyntheticEvent} event Event
*/
/** _enter(event) {
* Determine if the user is still dragging if(event.buttons !== 1) {
* @param {SyntheticEvent} event Event this.left = null;
*/ this.width = null;
_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
* Update the slider percentage */
* @param {number} pos Slider drag position _updatePercent(pos, width) {
* @param {number} width Slider width this.props.onChange(Math.min(Math.max(pos / width, 0), 1));
* @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({
* Update dimenions from rendered DOM outerWidth: this.node.getBoundingClientRect().width
*/ });
_updateDimensions() { }
this.setState({
outerWidth: findDOMNode(this).getBoundingClientRect().width /**
}); * Add listeners when about to mount
} */
componentWillMount() {
/** if (this.props.onResize) {
* Add listeners when about to mount this.resizeListener = this.props.onResize(this._updateDimensions);
*/ }
componentWillMount() { }
if (this.props.onResize) {
this.resizeListener = this.props.onResize(this._updateDimensions); /**
} * Trigger DOM updates on mount
} */
componentDidMount() {
/** this._updateDimensions();
* Trigger DOM updates on mount }
*/
componentDidMount() { /**
this._updateDimensions(); * Remove listeners on unmount
} */
componentWillUnmount() {
/** if (this.resizeListener) {
* Remove listeners on unmount this.resizeListener.remove();
*/ }
componentWillUnmount() { }
if (this.resizeListener) {
this.resizeListener.remove(); /**
} * Render the slider
} * @return {React.Component} The slider
*/
/** render() {
* Render the slider let outerWidth = this.state.outerWidth;
* @return {React.Component} The slider let { axis, axisUnit, min, max, scale } = this.props;
*/ let style = {
render() { width: '100%',
let outerWidth = this.state.outerWidth; height: axis ? '2.5em' : '1.5em',
let { axis, axisUnit, min, max, scale } = this.props; boxSizing: 'border-box'
};
let style = { if (!outerWidth) {
width: '100%', return <svg style={style} ref={node => this.node = node} />;
height: axis ? '2.5em' : '1.5em', }
boxSizing: 'border-box' let margin = MARGIN_LR * scale;
}; let width = outerWidth - (margin * 2);
let pctPos = width * this.props.percent;
if (!outerWidth) { return <div><svg
return <svg style={style} />; onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} style={style} ref={node => this.node = node} tabIndex="0">
} <rect className='primary' style={{ opacity: 0.3 }} x={margin} y='0.25em' rx='0.3em' ry='0.3em' width={width} height='0.7em' />
<rect className='primary-disabled' x={margin} y='0.45em' rx='0.15em' ry='0.15em' width={pctPos} height='0.3em' />
let margin = MARGIN_LR * scale; <circle className='primary' r={margin} cy='0.6em' cx={pctPos + margin} />
let width = outerWidth - (margin * 2); <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} />
let pctPos = width * this.props.percent; {axis && <g style={{ fontSize: '.7em' }}>
<text className='primary-disabled' y='3em' x={margin} style={{ textAnchor: 'middle' }}>{min + axisUnit}</text>
return <svg onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onTouchEnd={this._up} style={style}> <text className='primary-disabled' y='3em' x='50%' style={{ textAnchor: 'middle' }}>{(min + max / 2) + axisUnit}</text>
<rect className='primary' style={{ opacity: 0.3 }} x={margin} y='0.25em' rx='0.3em' ry='0.3em' width={width} height='0.7em' /> <text className='primary-disabled' y='3em' x='100%' style={{ textAnchor: 'end' }}>{max + axisUnit}</text>
<rect className='primary-disabled' x={margin} y='0.45em' rx='0.15em' ry='0.15em' width={pctPos} height='0.3em' /> </g>}
<circle className='primary' r={margin} cy='0.6em' cx={pctPos + margin} /> </svg>
<rect x={margin} width={width} height='100%' fillOpacity='0' style={{ cursor: 'col-resize' }} onMouseDown={this._down} onTouchMove={this._move} onTouchStart={this._down} /> </div>;
{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>;
}
}

View File

@@ -1,27 +1,38 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames'; import cn from 'classnames';
import { ListModifications, Modified } from './SvgIcons';
import AvailableModulesMenu from './AvailableModulesMenu'; import AvailableModulesMenu from './AvailableModulesMenu';
import { diffDetails } from '../utils/SlotFunctions'; import ModificationsMenu from './ModificationsMenu';
import { wrapCtxMenu } from '../utils/UtilityFunctions'; import { stopCtxPropagation, wrapCtxMenu } from '../utils/UtilityFunctions';
import { blueprintTooltip } from '../utils/BlueprintFunctions';
import { Module } from 'ed-forge';
import { TYPES } from 'ed-forge/lib/src/data/slots';
import autoBind from 'auto-bind';
import { toPairs } from 'lodash';
const HARDPOINT_SLOT_LABELS = {
1: 'S',
2: 'M',
3: 'L',
4: 'H',
};
/** /**
* Abstract Slot * Abstract Slot
*/ */
export default class Slot extends TranslatedComponent { export default class Slot extends TranslatedComponent {
static propTypes = { static propTypes = {
availableModules: React.PropTypes.func.isRequired, currentMenu: PropTypes.any,
onSelect: React.PropTypes.func.isRequired, hideSearch: PropTypes.bool,
onOpen: React.PropTypes.func.isRequired, m: PropTypes.instanceOf(Module),
maxClass: React.PropTypes.number.isRequired, warning: PropTypes.func,
selected: React.PropTypes.bool, drag: PropTypes.func,
m: React.PropTypes.object, drop: PropTypes.func,
ship: React.PropTypes.object.isRequired, dropClass: PropTypes.string,
warning: React.PropTypes.func, propsToShow: PropTypes.object.isRequired,
drag: React.PropTypes.func, onPropToggle: PropTypes.func.isRequired,
drop: React.PropTypes.func,
dropClass: React.PropTypes.string
}; };
/** /**
@@ -30,21 +41,122 @@ export default class Slot extends TranslatedComponent {
*/ */
constructor(props) { constructor(props) {
super(props); super(props);
autoBind(this);
this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this)); this.state = { menuIndex: 0 };
this._getMaxClassLabel = this._getMaxClassLabel.bind(this);
} }
// Must be implemented by subclasses: /**
// _getSlotDetails() * Opens a menu while setting state.
* @param {Object} newMenuIndex New menu index
* @param {Event} event Event object
*/
_openMenu(newMenuIndex, event) {
const slotName = this.props.m.getSlot();
if (
this.props.currentMenu === slotName &&
newMenuIndex === this.state.menuIndex
) {
this.context.closeMenu();
} {
this.setState({ menuIndex: newMenuIndex });
this.context.openMenu(slotName);
}
// If we don't stop event propagation, the underlying divs also might
// get clicked which would open up other menus
event.stopPropagation();
}
/** /**
* Get the CSS class name for the slot. Can/should be overriden * Generate the slot contents
* as necessary. * @return {React.Component} Slot contents
* @return {string} CSS Class name
*/ */
_getClassNames() { _getSlotDetails() {
return null; const { m, propsToShow } = this.props;
let { termtip, tooltip, language } = this.context;
const { translate, units, formats } = language;
if (m.isEmpty()) {
return <div className="empty">
{translate(
m.isOnSlot(TYPES.MILITARY) ? 'emptyrestricted' : 'empty'
)}
</div>;
} else {
let classRating = m.getClassRating();
let { drag, drop } = this.props;
// Modifications tooltip shows blueprint and grade, if available
let modTT = translate('modified');
const blueprint = m.getBlueprint();
const experimental = m.getExperimental();
const grade = m.getBlueprintGrade();
if (blueprint) {
modTT = `${translate(blueprint)} ${translate('grade')}: ${grade}`;
if (experimental) {
modTT += `, ${translate(experimental)}`;
}
modTT = (
<div>
<div>{modTT}</div>
{blueprintTooltip(language, m)}
</div>
);
}
let mass = m.get('mass') || m.get('cargo') || m.get('fuel') || 0;
return (
<div
className={cn('details', { disabled: !m.isEnabled() })}
draggable="true"
onDragStart={drag}
onDragEnd={drop}
>
<div className={'cb'}>
<div className={'l'}>
{classRating} {translate(m.readMeta('type'))}
{blueprint && (
<span
onMouseOver={termtip.bind(null, modTT)}
onMouseOut={tooltip.bind(null, null)}
>
<Modified />
</span>
)}
</div>
{propsToShow.mass ?
<div className={'r'}>
{formats.round(mass)}
{units.T}
</div> : null}
</div>
<div className={'cb'}>
{toPairs(propsToShow).sort().map(([prop, show]) => {
const { unit, value } = m.getFormatted(prop, true);
// Don't show mass again; it's already shown on the top right
// corner of a slot
if (!show || isNaN(value) || prop == 'mass') {
return null;
} else {
return (<div className='l'>
{translate(prop)}: {formats.round(value)}{unit}
</div>);
}
})}
{(m.getApplicableBlueprints() || []).length > 0 ? (
<div className="r">
<button onClick={this._openMenu.bind(this, 1)}
onContextMenu={stopCtxPropagation}
onMouseOver={termtip.bind(null, translate('modifications'))}
onMouseOut={tooltip.bind(null, null)}
>
<ListModifications />
</button>
</div>
) : null}
</div>
</div>
);
}
} }
/** /**
@@ -53,7 +165,19 @@ export default class Slot extends TranslatedComponent {
* @return {string} label * @return {string} label
*/ */
_getMaxClassLabel() { _getMaxClassLabel() {
return this.props.maxClass; const { m } = this.props;
let size = m.getSizeNum();
switch (true) {
case m.getSlot() === 'armour':
return '';
case size === 0:
// This can also happen for armour but that case was handled above
return 'U';
case m.isOnSlot(TYPES.HARDPOINT):
return HARDPOINT_SLOT_LABELS[size];
default:
return size;
}
} }
/** /**
@@ -63,7 +187,13 @@ export default class Slot extends TranslatedComponent {
_contextMenu(event) { _contextMenu(event) {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
this.props.onSelect(null,null); const { m } = this.props;
m.reset();
if (this.props.currentMenu === m.getSlot()) {
this.context.closeMenu();
} else {
this.forceUpdate();
}
} }
/** /**
@@ -73,36 +203,40 @@ export default class Slot extends TranslatedComponent {
render() { render() {
let language = this.context.language; let language = this.context.language;
let translate = language.translate; let translate = language.translate;
let { ship, m, dropClass, dragOver, onOpen, selected, onSelect, warning, shipMass, availableModules } = this.props; let {
let slotDetails, menu; currentMenu, m, dropClass, dragOver, warning, hideSearch, propsToShow,
onPropToggle,
if (m) { } = this.props;
slotDetails = this._getSlotDetails(m, translate, language.formats, language.units); // Must be implemented by sub classes const { menuIndex } = this.state;
} else {
slotDetails = <div className={'empty'}>{translate('empty')}</div>;
}
if (this.props.selected) {
menu = <AvailableModulesMenu
className={this._getClassNames()}
modules={availableModules()}
shipMass={ship.hullMass}
m={m}
onSelect={onSelect}
warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)}
/>;
}
// TODO: implement touch dragging // TODO: implement touch dragging
const selected = currentMenu === m.getSlot();
return ( return (
<div className={cn('slot', dropClass, { selected })} onClick={onOpen} onContextMenu={this._contextMenu} onDragOver={dragOver}> <div
<div className='details-container'> className={cn('slot', dropClass, { selected })}
<div className='sz'>{this._getMaxClassLabel(translate)}</div> onContextMenu={this._contextMenu}
{slotDetails} onDragOver={dragOver} tabIndex="0"
onClick={this._openMenu.bind(this, 0)}
>
<div className={cn(
'details-container',
{ warning: warning && warning(m) },
)}>
<div className="sz">{this._getMaxClassLabel(translate)}</div>
{this._getSlotDetails()}
</div> </div>
{menu} {selected && menuIndex === 0 &&
<AvailableModulesMenu
m={m} hideSearch={hideSearch}
onSelect={(item) => {
m.setItem(item);
this.context.closeMenu();
}}
warning={warning}
/>}
{selected && menuIndex === 1 &&
<ModificationsMenu m={m} propsToShow={propsToShow}
onPropToggle={onPropToggle} />}
</div> </div>
); );
} }

View File

@@ -1,40 +1,36 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import { wrapCtxMenu } from '../utils/UtilityFunctions'; import { wrapCtxMenu } from '../utils/UtilityFunctions';
import { Equalizer } from '../components/SvgIcons'; import { Equalizer } from '../components/SvgIcons';
import cn from 'classnames'; import cn from 'classnames';
import { Ship } from 'ed-forge';
import autoBind from 'auto-bind';
const browser = require('detect-browser');
/** /**
* Abstract Slot Section * Abstract Slot Section
*/ */
export default class SlotSection extends TranslatedComponent { export default class SlotSection extends TranslatedComponent {
static propTypes = { static propTypes = {
ship: React.PropTypes.object.isRequired, ship: PropTypes.instanceOf(Ship),
onChange: React.PropTypes.func.isRequired, code: PropTypes.string.isRequired,
code: React.PropTypes.string.isRequired, togglePwr: PropTypes.func,
togglePwr: React.PropTypes.func propsToShow: PropTypes.object.isRequired,
onPropToggle: PropTypes.func.isRequired,
}; };
/** /**
* Constructor * Constructor
* @param {Object} props React Component properties * @param {Object} props React Component properties
* @param {Object} context React Component context
* @param {string} sectionId Section DOM Id
* @param {string} sectionName Section name * @param {string} sectionName Section name
*/ */
constructor(props, context, sectionId, sectionName) { constructor(props, sectionName) {
super(props); super(props);
this.sectionId = sectionId; autoBind(this);
this.sectionName = sectionName; this.sectionName = sectionName;
this._getSlots = this._getSlots.bind(this);
this._selectModule = this._selectModule.bind(this);
this._getSectionMenu = this._getSectionMenu.bind(this);
this._contextMenu = this._contextMenu.bind(this);
this._drop = this._drop.bind(this);
this._dragOverNone = this._dragOverNone.bind(this);
this._close = this._close.bind(this);
this.state = {}; this.state = {};
} }
@@ -42,31 +38,7 @@ export default class SlotSection extends TranslatedComponent {
// _getSlots() // _getSlots()
// _getSectionMenu() // _getSectionMenu()
// _contextMenu() // _contextMenu()
// componentDidUpdate(prevProps)
/**
* Open a menu
* @param {string} menu Menu name
* @param {SyntheticEvent} event Event
*/
_openMenu(menu, event) {
event.preventDefault();
event.stopPropagation();
if (this.props.currentMenu === menu) {
menu = null;
}
this.context.openMenu(menu);
}
/**
* Mount/Use the specified module in the slot
* @param {Object} slot Slot
* @param {Object} m Selected module
*/
_selectModule(slot, m) {
this.props.ship.use(slot, m);
this.props.onChange();
this._close();
}
/** /**
* Slot Drag Handler * Slot Drag Handler
@@ -74,9 +46,11 @@ export default class SlotSection extends TranslatedComponent {
* @param {Event} e Drag Event * @param {Event} e Drag Event
*/ */
_drag(originSlot, e) { _drag(originSlot, e) {
e.dataTransfer.setData('text/html', e.currentTarget); if (!browser || (browser.name !== 'edge' && browser.name !== 'ie')) {
e.dataTransfer.effectAllowed = 'all'; e.dataTransfer.setData('text/html', e.currentTarget);
this.setState({ originSlot }); }
e.dataTransfer.effectAllowed = 'copyMove';
this.setState({ originSlot, copy: e.getModifierState('Alt') });
this._close(); this._close();
} }
@@ -90,10 +64,16 @@ export default class SlotSection extends TranslatedComponent {
e.stopPropagation(); e.stopPropagation();
let os = this.state.originSlot; let os = this.state.originSlot;
if (os) { if (os) {
e.dataTransfer.dropEffect = os != targetSlot && targetSlot.maxClass >= os.m.class ? 'copyMove' : 'none'; // Show correct icon
const effect = this.state.copy ? 'copy' : 'move';
if (!browser || (browser.name !== 'edge' && browser.name !== 'ie')) {
e.dataTransfer.dropEffect = os != targetSlot && canMount(this.props.ship, targetSlot, os.m.grp, os.m.class) ? effect : 'none';
}
this.setState({ targetSlot }); this.setState({ targetSlot });
} else { } else {
e.dataTransfer.dropEffect = 'none'; if (!browser || (browser.name !== 'edge' && browser.name !== 'ie')) {
e.dataTransfer.dropEffect = 'none';
}
} }
} }
@@ -103,7 +83,9 @@ export default class SlotSection extends TranslatedComponent {
*/ */
_dragOverNone(e) { _dragOverNone(e) {
e.preventDefault(); e.preventDefault();
e.dataTransfer.dropEffect = 'none'; if (!browser || (browser.name !== 'edge' && browser.name !== 'ie')) {
e.dataTransfer.dropEffect = 'none';
}
this.setState({ targetSlot: null }); this.setState({ targetSlot: null });
} }
@@ -113,20 +95,71 @@ export default class SlotSection extends TranslatedComponent {
* the origin slot will be empty. * the origin slot will be empty.
*/ */
_drop() { _drop() {
let { originSlot, targetSlot } = this.state; let { originSlot, targetSlot, copy } = this.state;
let m = originSlot.m; let m = originSlot.m;
if (targetSlot && m && targetSlot.maxClass >= m.class) { if (targetSlot && originSlot != targetSlot) {
// Swap modules if possible if (copy) {
if (targetSlot.m && originSlot.maxClass >= targetSlot.m.class) { // We want to copy the module in to the target slot
this.props.ship.use(originSlot, targetSlot.m, true); if (targetSlot && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
} else { // Otherwise empty the origin slot const mCopy = m.clone();
this.props.ship.use(originSlot, null, true); // Empty but prevent summary update 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;
}
} else {
// Store power info
const originEnabled = targetSlot.enabled;
const originPriority = targetSlot.priority;
const targetEnabled = originSlot.enabled;
const targetPriority = originSlot.priority;
// We want to move the module in to the target slot, and swap back any module that was originally in the target slot
if (targetSlot && m && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
// Swap modules if possible
if (targetSlot.m && canMount(this.props.ship, originSlot, targetSlot.m.grp, targetSlot.m.class)) {
this.props.ship.use(originSlot, targetSlot.m, true);
this.props.ship.use(targetSlot, m);
// Swap power
originSlot.enabled = originEnabled;
originSlot.priority = originPriority;
targetSlot.enabled = targetEnabled;
targetSlot.priority = targetPriority;
} else { // Otherwise empty the origin slot
// Store power
const targetEnabled = originSlot.enabled;
this.props.ship.use(originSlot, null, true); // Empty but prevent summary update
this.props.ship.use(targetSlot, m);
originSlot.enabled = 0;
originSlot.priority = 0;
targetSlot.enabled = targetEnabled;
targetSlot.priority = targetPriority;
}
this.props.ship
.updatePowerGenerated()
.updatePowerUsed()
.recalculateMass()
.updateJumpStats()
.recalculateShield()
.recalculateShieldCells()
.recalculateArmour()
.recalculateDps()
.recalculateEps()
.recalculateHps()
.updateMovement();
}
} }
this.props.ship.use(targetSlot, m); // update target slot
this.props.onChange();
} }
this.setState({ originSlot: null, targetSlot: null }); this.setState({ originSlot: null, targetSlot: null, copy: null });
} }
/** /**
@@ -141,12 +174,12 @@ export default class SlotSection extends TranslatedComponent {
return null; return null;
} }
if (slot === originSlot) { if (slot === originSlot) {
if (targetSlot && targetSlot.m && originSlot.maxClass < targetSlot.m.class) { if (targetSlot && targetSlot.m && !canMount(this.props.ship, originSlot, targetSlot.m.grp, targetSlot.m.class)) {
return 'dropEmpty'; // Origin slot will be emptied return 'dropEmpty'; // Origin slot will be emptied
} }
return null; return null;
} }
if (originSlot.m && slot.maxClass >= originSlot.m.class) { // Eligble drop slot if (originSlot.m && canMount(this.props.ship, slot, originSlot.m.grp, originSlot.m.class)) { // Eligble drop slot
if (slot === targetSlot) { if (slot === targetSlot) {
return 'drop'; // Can drop return 'drop'; // Can drop
} }
@@ -156,6 +189,17 @@ export default class SlotSection extends TranslatedComponent {
return 'ineligible'; // Cannot be dropped / invalid drop slot return 'ineligible'; // Cannot be dropped / invalid drop slot
} }
_open(newMenu, event) {
event.preventDefault();
event.stopPropagation();
const { currentMenu } = this.props;
if (currentMenu === newMenu) {
this.context.closeMenu();
} else {
this.context.openMenu(newMenu);
}
}
/** /**
* Close current menu * Close current menu
*/ */
@@ -172,14 +216,13 @@ export default class SlotSection extends TranslatedComponent {
render() { render() {
let translate = this.context.language.translate; let translate = this.context.language.translate;
let sectionMenuOpened = this.props.currentMenu === this.sectionName; let sectionMenuOpened = this.props.currentMenu === this.sectionName;
let open = this._openMenu.bind(this, this.sectionName);
let ctx = wrapCtxMenu(this._contextMenu);
return ( return (
<div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}> <div className="group" onDragLeave={this._dragOverNone}>
<div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}> <div className={cn('section-menu', { selected: sectionMenuOpened })}
<h1>{translate(this.sectionName)} <Equalizer/></h1> onContextMenu={wrapCtxMenu(this._contextMenu)} onClick={this._open.bind(this, this.sectionName)}>
{sectionMenuOpened ? this._getSectionMenu(translate) : null } <h1 tabIndex="0">{translate(this.sectionName)}<Equalizer/></h1>
{sectionMenuOpened && this._getSectionMenu()}
</div> </div>
{this._getSlots()} {this._getSlots()}
</div> </div>

View File

@@ -1,72 +0,0 @@
import React from 'react';
import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
import { jumpRange } from '../shipyard/Calculations';
import { diffDetails } from '../utils/SlotFunctions';
import AvailableModulesMenu from './AvailableModulesMenu';
/**
* Standard Slot
*/
export default class StandardSlot extends TranslatedComponent {
static propTypes = {
slot: React.PropTypes.object,
modules: React.PropTypes.array.isRequired,
onSelect: React.PropTypes.func.isRequired,
onOpen: React.PropTypes.func.isRequired,
ship: React.PropTypes.object.isRequired,
selected: React.PropTypes.bool,
warning: React.PropTypes.func,
};
/**
* Render the slot
* @return {React.Component} Slot component
*/
render() {
let { translate, formats, units } = this.context.language;
let { modules, slot, warning, onSelect, ladenMass, ship } = this.props;
let m = slot.m;
let classRating = m.class + m.rating;
let menu;
if (this.props.selected) {
menu = <AvailableModulesMenu
className='standard'
modules={modules}
shipMass={ship.ladenMass}
m={m}
onSelect={onSelect}
warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)}
/>;
}
return (
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen}>
<div className={cn('details-container', { warning: warning && warning(slot.m) })}>
<div className={'sz'}>{slot.maxClass}</div>
<div>
<div className='l'>{classRating} {translate(m.grp == 'bh' ? m.grp : m.name || m.grp)}</div>
<div className={'r'}>{m.mass || m.fuel || 0}{units.T}</div>
<div className={'cb'}>
{ m.grp == 'bh' && m.name ? <div className='l'>{translate(m.name)}</div> : null }
{ m.optmass ? <div className='l'>{translate('optimal mass')}: {m.optmass}{units.T}</div> : null }
{ m.maxmass ? <div className='l'>{translate('max mass')}: {m.maxmass}{units.T}</div> : null }
{ m.range ? <div className='l'>{translate('range')}: {m.range}{units.km}</div> : null }
{ m.time ? <div className='l'>{translate('time')}: {formats.time(m.time)}</div> : null }
{ m.eff ? <div className='l'>{translate('efficiency')}: {m.eff}</div> : null }
{ m.pGen ? <div className='l'>{translate('power')}: {m.pGen}{units.MW}</div> : null }
{ m.maxfuel ? <div className='l'>{translate('max')} {translate('fuel')}: {m.maxfuel}{units.T}</div> : null }
{ m.weaponcapacity ? <div className='l'>{translate('WEP')}: {m.weaponcapacity}{units.MJ} / {m.weaponrecharge}{units.MW}</div> : null }
{ m.systemcapacity ? <div className='l'>{translate('SYS')}: {m.systemcapacity}{units.MJ} / {m.systemrecharge}{units.MW}</div> : null }
{ m.enginecapacity ? <div className='l'>{translate('ENG')}: {m.enginecapacity}{units.MJ} / {m.enginerecharge}{units.MW}</div> : null }
</div>
</div>
</div>
{menu}
</div>
);
}
}

View File

@@ -1,84 +1,67 @@
import React from 'react'; import React from 'react';
import cn from 'classnames';
import SlotSection from './SlotSection'; import SlotSection from './SlotSection';
import StandardSlot from './StandardSlot'; import Slot from './Slot';
import { diffDetails } from '../utils/SlotFunctions'; import autoBind from 'auto-bind';
import * as ModuleUtils from '../shipyard/ModuleUtils'; import { stopCtxPropagation, moduleGet } from '../utils/UtilityFunctions';
import * as ShipRoles from '../shipyard/ShipRoles'; import { ShipProps, Module } from 'ed-forge';
import { stopCtxPropagation } from '../utils/UtilityFunctions'; import { getModuleInfo } from 'ed-forge/lib/src/data/items';
const { CONSUMED_RETR, LADEN_MASS } = ShipProps;
/** /**
* Standard Slot section * Standard Slot section
*/ */
export default class StandardSlotSection extends SlotSection { export default class StandardSlotSection extends SlotSection {
/** /**
* Constructor * Constructor
* @param {Object} props React Component properties * @param {Object} props React Component properties
* @param {Object} context React Component context * @param {Object} context React Component context
*/ */
constructor(props, context) { constructor(props) {
super(props, context, 'standard', 'standard'); super(props, 'core internal');
this._optimizeStandard = this._optimizeStandard.bind(this); autoBind(this);
this._selectBulkhead = this._selectBulkhead.bind(this);
} }
/** /**
* Use the lightest/optimal available standard modules * Resets all modules of the ship
*/ */
_optimizeStandard() { _emptyAll() {
this.props.ship.useLightestStandard(); this.props.ship.getModules().forEach((slot) => slot.reset());
this.props.onChange();
this._close(); this._close();
} }
/** /**
* Fill all standard slots with the specificed rating (using max class) * Sets all modules to a specific rating
* @param {Boolean} shielded True if shield generator should be included * @param {string} rating Module rating to set
* @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames * @param {string} fsdPPException Custom rating for FSD
*/ */
_multiPurpose(shielded, bulkheadIndex) { _nRated(rating, fsdPPException) {
ShipRoles.multiPurpose(this.props.ship, shielded, bulkheadIndex); const { ship } = this.props;
this.props.onChange(); const pp = ship.getPowerPlant();
pp.setItem('powerplant', pp.getSize(), fsdPPException || rating);
const eng = ship.getThrusters();
eng.setItem('thrusters', eng.getSize(), rating);
const fsd = ship.getFSD();
fsd.setItem('fsd', fsd.getSize(), fsdPPException || rating);
const ls = ship.getLifeSupport();
ls.setItem('lifesupport', ls.getSize(), rating);
const pd = ship.getPowerDistributor();
pd.setItem('powerdistributor', pd.getSize(), rating);
const sen = ship.getSensors();
sen.setItem('sensors', sen.getSize(), rating);
this._close(); this._close();
} }
/** /**
* Trader Build * Creates a new slot for a given module.
* @param {Boolean} shielded True if shield generator should be included * @param {Module} m Module to create the slot for
* @param {function} warning Warning callback
* @return {React.Component} Slot component
*/ */
_optimizeCargo(shielded) { _mkSlot(m, warning) {
ShipRoles.trader(this.props.ship, shielded); const { currentMenu, propsToShow, onPropToggle } = this.props;
this.props.onChange(); return <Slot key={m.getSlot()} m={m} warning={warning} hideSearch={true}
this._close(); currentMenu={currentMenu} propsToShow={propsToShow} onPropToggle={onPropToggle}
} />;
/**
* Explorer role
* @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included
*/
_optimizeExplorer(planetary) {
ShipRoles.explorer(this.props.ship, planetary);
this.props.onChange();
this._close();
}
/**
* Use the specified bulkhead
* @param {Object} bulkhead Bulkhead module details
*/
_selectBulkhead(bulkhead) {
this.props.ship.useBulkhead(bulkhead.index);
this.context.tooltip();
this.props.onChange();
this._close();
}
/**
* On right click optimize the standard modules
*/
_contextMenu() {
this._optimizeStandard();
} }
/** /**
@@ -86,103 +69,30 @@ export default class StandardSlotSection extends SlotSection {
* @return {Array} Array of Slots * @return {Array} Array of Slots
*/ */
_getSlots() { _getSlots() {
let { translate, units, formats } = this.context.language; const { ship } = this.props;
let { ship, currentMenu } = this.props; const fsd = ship.getFSD();
let slots = new Array(8); return [
let open = this._openMenu; this._mkSlot(ship.getAlloys()),
let select = this._selectModule; this._mkSlot(
let selBulkhead = this._selectBulkhead; ship.getPowerPlant(),
let st = ship.standard; (m) => moduleGet(m, 'powercapacity') < ship.get(CONSUMED_RETR),
let avail = ship.getAvailableModules().standard; ),
let bh = ship.bulkheads; this._mkSlot(
ship.getThrusters(),
slots[0] = <StandardSlot (m) => moduleGet(m, 'enginemaximalmass') < ship.get(LADEN_MASS),
key='bh' ),
slot={bh} this._mkSlot(fsd),
modules={ship.getAvailableModules().bulkheads} this._mkSlot(
onOpen={open.bind(this, bh)} ship.getPowerDistributor(),
onSelect={this._selectBulkhead} (m) => moduleGet(m, 'enginescapacity') <= ship.readProp('boostenergy'),
selected={currentMenu == bh} ),
ship={ship} this._mkSlot(ship.getLifeSupport()),
/>; this._mkSlot(ship.getSensors()),
this._mkSlot(
slots[1] = <StandardSlot ship.getCoreFuelTank(),
key='pp' (m) => moduleGet(m, 'fuel') < fsd.get('maxfuel')
slot={st[0]} ),
modules={avail[0]} ];
onOpen={open.bind(this, st[0])}
onSelect={select.bind(this, st[0])}
selected={currentMenu == st[0]}
ship={ship}
warning={m => m.pGen < ship.powerRetracted}
/>;
slots[2] = <StandardSlot
key='th'
slot={st[1]}
modules={avail[1]}
onOpen={open.bind(this, st[1])}
onSelect={select.bind(this, st[1])}
selected={currentMenu == st[1]}
ship={ship}
warning={m => m.maxmass < (ship.ladenMass - st[1].mass + m.mass)}
/>;
slots[3] = <StandardSlot
key='fsd'
slot={st[2]}
modules={avail[2]}
onOpen={open.bind(this, st[2])}
onSelect={select.bind(this, st[2])}
ship={ship}
selected={currentMenu == st[2]}
/>;
slots[4] = <StandardSlot
key='ls'
slot={st[3]}
modules={avail[3]}
onOpen={open.bind(this, st[3])}
onSelect={select.bind(this, st[3])}
ship={ship}
selected={currentMenu == st[3]}
/>;
slots[5] = <StandardSlot
key='pd'
slot={st[4]}
modules={avail[4]}
onOpen={open.bind(this, st[4])}
onSelect={select.bind(this, st[4])}
selected={currentMenu == st[4]}
ship={ship}
warning= {m => m.enginecapacity < ship.boostEnergy}
/>;
slots[6] = <StandardSlot
key='ss'
slot={st[5]}
modules={avail[5]}
onOpen={open.bind(this, st[5])}
onSelect={select.bind(this, st[5])}
selected={currentMenu == st[5]}
ship={ship}
warning= {m => m.enginecapacity < ship.boostEnergy}
/>;
slots[7] = <StandardSlot
key='ft'
slot={st[6]}
modules={avail[6]}
onOpen={open.bind(this, st[6])}
onSelect={select.bind(this, st[6])}
selected={currentMenu == st[6]}
ship={ship}
warning= {m => m.fuel < st[2].m.maxfuel} // Show warning when fuel tank is smaller than FSD Max Fuel
/>;
return slots;
} }
/** /**
@@ -190,22 +100,18 @@ export default class StandardSlotSection extends SlotSection {
* @param {Function} translate Translate function * @param {Function} translate Translate function
* @return {React.Component} Section menu * @return {React.Component} Section menu
*/ */
_getSectionMenu(translate) { _getSectionMenu() {
let planetaryDisabled = this.props.ship.internal.length < 4; const { translate } = this.context.language;
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}> return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul> <ul>
<li className='lc' onClick={this._optimizeStandard}>{translate('Maximize Jump Range')}</li> <li className='lc' tabIndex="0" onClick={this._emptyAll}>{translate('empty all slots')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('roles')}</div> <div className='select-group cap'>{translate('core')}</div>
<ul> <ul>
<li className='lc' onClick={this._multiPurpose.bind(this, false, 0)}>{translate('Multi-purpose')}</li> <li className='lc' tabIndex="0" onClick={this._nRated.bind(this, '5', undefined)}>{translate('A-rated')}</li>
<li className='lc' onClick={this._multiPurpose.bind(this, true, 2)}>{translate('Combat')}</li> <li className='lc' tabIndex="0" onClick={this._nRated.bind(this, '2', undefined)}>{translate('D-rated')}</li>
<li className='lc' onClick={this._optimizeCargo.bind(this, false)}>{translate('Trader')}</li> <li className='lc' tabIndex="0" onClick={this._nRated.bind(this, '2', '5')}>{translate('D-rated + A-rated FSD/PP')}</li>
<li className='lc' onClick={this._optimizeCargo.bind(this, true)}>{translate('Shielded Trader')}</li>
<li className='lc' onClick={this._optimizeExplorer.bind(this, false)}>{translate('Explorer')}</li>
<li className={cn('lc', { disabled: planetaryDisabled })} onClick={!planetaryDisabled && this._optimizeExplorer.bind(this, true)}>{translate('Planetary Explorer')}</li>
</ul> </ul>
</div>; </div>;
} }
} }

View File

@@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames'; import cn from 'classnames';
import { shallowEqual } from '../utils/UtilityFunctions'; import { shallowEqual } from '../utils/UtilityFunctions';
@@ -8,8 +9,8 @@ import { shallowEqual } from '../utils/UtilityFunctions';
class SvgIcon extends React.Component { class SvgIcon extends React.Component {
static propTypes = { static propTypes = {
className: React.PropTypes.any, className: PropTypes.any,
style: React.PropTypes.object style: PropTypes.object
}; };
/** /**
@@ -64,8 +65,8 @@ export class CoriolisLogo extends SvgIcon {
*/ */
svg() { svg() {
return <g transform='translate(1,1)'> return <g transform='translate(1,1)'>
<path stroke='#ff3b00' transform='rotate(45 15 15)' d='m4,4 l 11,-4 l 11,4 l 4,11 l -4,11 l -11,4 l -11,-4 l -4,-11 l 4,-11 l 22,0 l 0,22 l -22,0 z' strokeWidth='1' fill='#000000'/> <path stroke='#0a8bd6' transform='rotate(45 15 15)' d='m4,4 l 11,-4 l 11,4 l 4,11 l -4,11 l -11,4 l -11,-4 l -4,-11 l 4,-11 l 22,0 l 0,22 l -22,0 z' strokeWidth='1' fill='#000000'/>
<rect height='3' width='10' y='13.5' x='10' strokeWidth='1' stroke='#ff3b00'/> <rect height='3' width='10' y='13.5' x='10' strokeWidth='1' stroke='#0a8bd6'/>
</g>; </g>;
} }
} }
@@ -227,6 +228,116 @@ export class LinkIcon extends SvgIcon {
} }
} }
/**
* Link / Permalink / Chain
*/
export class OrbisIcon extends SvgIcon {
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return (
<g transform="scale(.037296)">
<path d="m429 319c60.75 0 110 49.248 110 110 0 60.75-49.247 110-110 110s-110-49.247-110-110c0-60.749 49.248-110 110-110m0-20c-34.724 0-67.369 13.522-91.922 38.075s-38.075 57.198-38.075 91.922 13.522 67.369 38.075 91.922c24.553 24.554 57.198 38.075 91.922 38.075s67.369-13.521 91.922-38.075c24.554-24.553 38.075-57.198 38.075-91.922s-13.521-67.369-38.075-91.922c-24.553-24.553-57.198-38.075-91.922-38.075z" />
<path d="m429 235c107.14 0 194 86.855 194 194s-86.855 194-194 194-194-86.855-194-194 86.855-194 194-194m0-20c-28.881 0-56.908 5.661-83.304 16.825-25.485 10.779-48.368 26.207-68.016 45.853-19.646 19.647-35.074 42.53-45.853 68.015-11.163 26.396-16.824 54.423-16.824 83.304s5.661 56.908 16.825 83.304c10.779 25.484 26.207 48.368 45.853 68.016 19.647 19.646 42.53 35.073 68.015 45.854 26.396 11.164 54.423 16.825 83.304 16.825s56.908-5.661 83.304-16.825c25.484-10.779 48.368-26.206 68.016-45.854 19.646-19.646 35.073-42.53 45.854-68.016 11.164-26.396 16.825-54.423 16.825-83.304s-5.661-56.908-16.825-83.304c-10.779-25.485-26.206-48.368-45.854-68.015-19.646-19.646-42.53-35.074-68.016-45.853-26.396-11.164-54.423-16.825-83.304-16.825z" />
<path d="m429 63c202.14 0 366 163.86 366 366s-163.86 366-366 366-366-163.86-366-366 163.86-366 366-366m0-20c-52.101 0-102.65 10.208-150.25 30.342-45.966 19.442-87.244 47.271-122.69 82.714s-63.272 76.721-82.714 122.69c-20.134 47.601-30.342 98.153-30.342 150.25s10.208 102.65 30.342 150.25c19.442 45.967 47.271 87.244 82.714 122.69 35.443 35.442 76.721 63.271 122.69 82.715 47.601 20.133 98.153 30.342 150.25 30.342s102.65-10.209 150.25-30.342c45.967-19.442 87.244-47.271 122.69-82.715 35.441-35.442 63.271-76.721 82.714-122.69 20.133-47.601 30.342-98.153 30.342-150.25s-10.209-102.65-30.342-150.25c-19.442-45.966-47.271-87.244-82.714-122.69s-76.722-63.272-122.69-82.714c-47.601-20.134-98.153-30.342-150.25-30.342z"/>
<path d="m429 63c202.14 0 366 163.86 366 366s-163.86 366-366 366-366-163.86-366-366 163.86-366 366-366m0-20c-52.101 0-102.65 10.208-150.25 30.342-45.966 19.442-87.244 47.271-122.69 82.714s-63.272 76.721-82.714 122.69c-20.134 47.601-30.342 98.153-30.342 150.25s10.208 102.65 30.342 150.25c19.442 45.967 47.271 87.244 82.714 122.69 35.443 35.442 76.721 63.271 122.69 82.715 47.601 20.133 98.153 30.342 150.25 30.342s102.65-10.209 150.25-30.342c45.967-19.442 87.244-47.271 122.69-82.715 35.441-35.442 63.271-76.721 82.714-122.69 20.133-47.601 30.342-98.153 30.342-150.25s-10.209-102.65-30.342-150.25c-19.442-45.966-47.271-87.244-82.714-122.69s-76.722-63.272-122.69-82.714c-47.601-20.134-98.153-30.342-150.25-30.342z" />
<path d="m429 20c225.88 0 409 183.11 409 409s-183.11 409-409 409-409-183.11-409-409 183.11-409 409-409m0-20c-57.905 0-114.09 11.345-166.99 33.721-51.087 21.608-96.963 52.538-136.36 91.93s-70.321 85.269-91.93 136.36c-22.376 52.902-33.721 109.09-33.721 166.99s11.345 114.09 33.721 166.99c21.608 51.087 52.538 96.964 91.93 136.35 39.392 39.392 85.269 70.321 136.36 91.931 52.902 22.375 109.09 33.721 166.99 33.721s114.09-11.346 166.99-33.721c51.087-21.608 96.964-52.538 136.35-91.931 39.392-39.392 70.321-85.269 91.931-136.35 22.375-52.902 33.721-109.09 33.721-166.99s-11.346-114.09-33.721-166.99c-21.608-51.087-52.538-96.963-91.931-136.36-39.392-39.392-85.269-70.321-136.35-91.93-52.902-22.376-109.09-33.721-166.99-33.721z"/>
<path d="m155.34 679.12 173.25-190.21-15.626-13.721-170.9 190.4zm31.01 31.714 202.41-169.1-16.418-14.417-198.76 170.43z"/>
<path d="m702.66 178.87-173.25 190.21 15.625 13.721 170.9-190.4zm-31.01-31.714-202.41 169.1 16.418 14.417 198.76-170.43z" />
<rect transform="matrix(-.7071 -.7071 .7071 -.7071 429.34 1036.2)" x="387.09" y="420.77" width="84.379" height="16.859" />
</g>);
}
}
/**
* Material
*/
export class MatIcon extends SvgIcon {
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return<g xmlns="http://www.w3.org/2000/svg">
<path d="M 24.86,4.18
C 24.86,4.18 17.17,7.82 17.17,7.82
17.17,7.82 15.35,14.55 15.35,14.55
15.35,14.55 24.70,9.75 24.70,9.75
24.70,9.75 24.86,4.18 24.86,4.18 Z
M 32.21,17.45
C 32.21,17.45 26.41,11.18 26.41,11.18
26.41,11.18 19.51,11.51 19.51,11.51
19.51,11.51 26.92,19.01 26.92,19.01
26.92,19.01 32.21,17.45 32.21,17.45 Z
M 21.99,28.62
C 21.99,28.62 26.10,21.10 26.10,21.10
26.10,21.10 23.66,14.57 23.66,14.57
23.66,14.57 18.89,24.01 18.89,24.01
18.89,24.01 21.99,28.62 21.99,28.62 Z
M 8.33,22.24
C 8.33,22.24 16.67,23.87 16.67,23.87
16.67,23.87 22.06,19.51 22.06,19.51
22.06,19.51 11.70,17.84 11.70,17.84
11.70,17.84 8.33,22.24 8.33,22.24 Z
M 10.11,7.14
C 10.11,7.14 11.15,15.66 11.15,15.66
11.15,15.66 16.92,19.49 16.92,19.49
16.92,19.49 15.29,9.02 15.29,9.02
15.29,9.02 10.11,7.14 10.11,7.14 Z
M 27.69,2.67
C 27.69,2.67 35.89,16.00 35.89,16.00
35.89,16.00 27.69,29.33 27.69,29.33
27.69,29.33 11.31,29.33 11.31,29.33
11.31,29.33 3.11,16.00 3.11,16.00
3.11,16.00 11.31,2.67 11.31,2.67
11.31,2.67 27.67,2.67 27.67,2.67M 29.16,0.00
C 29.16,0.00 27.69,0.00 27.69,0.00
27.69,0.00 11.31,0.00 11.31,0.00
11.31,0.00 9.84,0.00 9.84,0.00
9.84,0.00 9.06,1.25 9.06,1.25
9.06,1.25 0.87,14.57 0.87,14.57
0.87,14.57 0.00,15.98 0.00,15.98
0.00,15.98 0.87,17.39 0.87,17.39
0.87,17.39 9.06,30.73 9.06,30.73
9.06,30.73 9.84,32.00 9.84,32.00
9.84,32.00 11.31,32.00 11.31,32.00
11.31,32.00 27.69,32.00 27.69,32.00
27.69,32.00 29.16,32.00 29.16,32.00
29.16,32.00 29.94,30.73 29.94,30.73
29.94,30.73 38.13,17.39 38.13,17.39
38.13,17.39 39.00,15.98 39.00,15.98
39.00,15.98 38.13,14.57 38.13,14.57
38.13,14.57 29.94,1.25 29.94,1.25
29.94,1.25 29.16,0.00 29.16,0.00
29.16,0.00 29.16,0.00 29.16,0.00 Z" />
</g>;
}
}
/**
* Shopping icon (dollar sign)
*/
export class ShoppingIcon extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<path d='M94 188v-17c-9-1-16-3-21-6-6-3-11-7-15-14-4-6-6-14-6-23l17-3c2 9 4 16 7 21 5 6 11 9 18 10v-56c-7-1-14-4-22-8-6-3-10-8-13-13-3-6-4-12-4-19 0-13 4-23 13-31 6-5 15-8 26-9v-8h11v8c10 1 18 4 24 9 8 6 12 15 14 26l-18 3c-1-7-4-12-7-16s-8-6-13-7v50l17 6c6 2 10 5 13 8 4 4 7 8 8 13 2 4 3 10 3 15 0 12-4 22-11 31-8 8-18 12-30 13v17H94zm0-153c-7 1-12 3-16 8-4 4-6 9-6 15s2 11 5 16c4 4 9 7 17 9V35zm11 121a28 28 0 0 0 24-28c-1-6-2-11-6-15-3-4-9-7-18-10v53z'/>
</g>;
}
}
/** /**
* No Power - Lightning bolt + no entry * No Power - Lightning bolt + no entry
*/ */
@@ -319,6 +430,108 @@ export class Warning extends SvgIcon {
} }
} }
/**
* Absolute damage
*/
export class DamageAbsolute extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <ellipse cx='100' cy='100' rx='90' ry='90' fillOpacity='0' />;
}
}
/**
* Thermal damage
*/
export class DamageThermal extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<ellipse cx='100' cy='100' rx='90' ry='90' fillOpacity='0' />
<ellipse cx='100' cy='100' rx='30' ry='30' fillOpacity='1' />
<path d='M100 20v80' />
</g>;
}
}
/**
* Kinetic damage
*/
export class DamageKinetic extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<ellipse cx='100' cy='100' rx='90' ry='90' fillOpacity='0' />
<ellipse cx='62' cy='67' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='62' cy='101' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='62' cy='135' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='50' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='84' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='118' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='152' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='138' cy='67' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='138' cy='101' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='138' cy='135' rx='5' ry='5' fillOpacity='1' />
</g>;
}
}
/**
* Explosive damage
*/
export class DamageExplosive extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<ellipse cx='100' cy='100' rx='50' ry='50' fillOpacity='0' />
<ellipse cx='100' cy='20' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='156.57' cy='36.57' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='180' cy='100' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='156.57' cy='163.43' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='180' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='43.43' cy='163.43' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='20' cy='100' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='43.43' cy='36.57' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='75' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='125' cy='100' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='125' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='75' cy='100' rx='5' ry='5' fillOpacity='1' />
</g>;
}
}
/** /**
* Fixed mount hardpoint * Fixed mount hardpoint
*/ */
@@ -334,11 +547,11 @@ export class MountFixed extends SvgIcon {
*/ */
svg() { svg() {
return <g> return <g>
<circle fillOpacity='0' r='70' cy='100' cx='100' strokeWidth='5' /> <circle cx='100' cy='100' r='76' fillOpacity='0' />
<line y2='60' x2='101' y1='0' x1='101' strokeWidth='5' /> <path d='M0 100h48' />
<line y2='101' x2='200' y1='101' x1='140' strokeWidth='5' /> <path d='M152 100h48' />
<line y2='101' x2='60' y1='101' x1='0' strokeWidth='5' /> <path d='M100 0v48' />
<line y2='200' x2='101' y1='140' x1='101' strokeWidth='5' /> <path d='M100 152v48' />
</g>; </g>;
} }
} }
@@ -358,8 +571,8 @@ export class MountGimballed extends SvgIcon {
*/ */
svg() { svg() {
return <g> return <g>
<ellipse ry='25' rx='95' cy='100' cx='100' fillOpacity='0' strokeWidth='5' /> <ellipse cx='100' cy='100' rx='90' ry='25' fillOpacity='0' />
<ellipse ry='95' rx='25' cy='100' cx='100' fillOpacity='0' strokeWidth='5' /> <ellipse cx='100' cy='100' rx='20' ry='95' fillOpacity='0' />
</g>; </g>;
} }
} }
@@ -379,13 +592,64 @@ export class MountTurret extends SvgIcon {
*/ */
svg() { svg() {
return <g> return <g>
<line y2='170' x2='162' y1='170' x1='8' strokeWidth='6' /> <path d='M40 50 A 40 40 0 0 0 0 90' />
<path d='m13,138l144,0l0,-50l-27,-40l-90,0l-27,40l0,50z' id='svg_12' fillOpacity='0' strokeWidth='6' /> <path d='M40 50h40' />
<line y2='91' x2='200' y1='91' x1='159' strokeWidth='6' /> <path d='M120 90 A 40 40 0 0 0 80 50' />
<path d='M0 90v40' />
<path d='M120 90v40' />
<path d='M0 120h120' />
<path d='M120 90h80' />
<path d='M0 160h120' />
</g>; </g>;
} }
} }
/**
* Collapse section
*/
export class CollapseSection extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<path d='m 100,180 0,-140' />
<path d='m 100,40 25,45' />
<path d='m 100,40 -25,45' />
<path d='m 20,20 160,0' />
</g>;
}
}
/**
* Expand section
*/
export class ExpandSection extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<path d='m 100,20 0,140' />
<path d='m 100,160 25,-45' />
<path d='m 100,160 -25,-45' />
<path d='m 20,180 160,0' />
</g>;
}
}
/** /**
* Rocket ship * Rocket ship
*/ */
@@ -399,6 +663,90 @@ export class Rocket extends SvgIcon {
} }
} }
/**
* Help
*/
export class Help extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <path d="M90.381 140.644a225.03 225.03 0 0 1-.106-5.706c0-7.47 1.058-13.92 3.172-19.346 1.55-4.087 4.052-8.21 7.505-12.367 2.536-3.031 7.1-7.453 13.688-13.267 6.59-5.815 10.872-10.448 12.844-13.9 1.974-3.453 2.96-7.224 2.96-11.312 0-7.4-2.89-13.9-8.667-19.503-5.779-5.602-12.862-8.403-21.248-8.403-8.104 0-14.87 2.536-20.296 7.61-5.427 5.074-8.986 13.003-10.677 23.784L50 65.91c1.762-14.448 6.995-25.511 15.698-33.194 8.704-7.68 20.208-11.522 34.514-11.522 15.152 0 27.237 4.124 36.258 12.369C145.49 41.807 150 51.779 150 63.477c0 6.766-1.585 13.003-4.756 18.712-3.17 5.708-9.372 12.65-18.605 20.824-6.201 5.496-10.253 9.55-12.156 12.156-1.904 2.609-3.312 5.603-4.23 8.986-.915 3.383-1.443 8.88-1.584 16.49H90.38zm-1.163 38.162v-21.67h21.67v21.67h-21.67z"/>;
}
}
/**
* ListModifications (engineers)
*/
export class ListModifications extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Render the Icon
* @return {React.Component} SVG Icon
*/
render() {
return (
<svg className={cn('modicon', this.props.className)} style={this.props.style} viewBox={this.viewBox()}>
{this.svg()}
</svg>
);
}
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <path d='M20 180l90-100-90 100zM176 40a40 40 0 1 1-27-28'/>;
}
}
/**
* Modified (engineers)
*/
export class Modified extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Render the Icon
* @return {React.Component} SVG Icon
*/
render() {
return (
<svg className={cn('modicon', this.props.className)} style={this.props.style} viewBox={this.viewBox()}>
{this.svg()}
</svg>
);
}
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<path d="M100,5L18,52.5L18,147.5L100,195L182,147.5L182,52.5L100,5Z"/>
<path d="M100,70L74,85L74,115L100,130L126,115L126,85L100,70Z"/>
</g>;
}
}
/** /**
* Hammer * Hammer
*/ */
@@ -451,6 +799,24 @@ export class Switch extends SvgIcon {
} }
} }
/**
* Pip
*/
export class Pip extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <rect x='10' y='10' width='180' height='180'/>;
}
}
/** /**
* In-game Coriolis Station logo * In-game Coriolis Station logo
*/ */

View File

@@ -1,14 +1,14 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
/** /**
* Document Root Tooltip * Document Root Tooltip
*/ */
export default class Tooltip extends TranslatedComponent { export default class Tooltip extends TranslatedComponent {
static propTypes = { static propTypes = {
rect: React.PropTypes.object.isRequired, rect: PropTypes.object.isRequired,
options: React.PropTypes.object options: PropTypes.object
}; };
static defaultProps = { static defaultProps = {
@@ -126,5 +126,4 @@ export default class Tooltip extends TranslatedComponent {
</div> </div>
</div>; </div>;
} }
}
}

View File

@@ -1,21 +1,21 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { shallowEqual } from '../utils/UtilityFunctions'; import { shallowEqual } from '../utils/UtilityFunctions';
/** /**
* Abstract Translated Component * Abstract Translated Component
*/ */
export default class TranslatedComponent extends React.Component { export default class TranslatedComponent extends React.Component {
static contextTypes = { static contextTypes = {
language: React.PropTypes.object.isRequired, language: PropTypes.object.isRequired,
sizeRatio: React.PropTypes.number.isRequired, sizeRatio: PropTypes.number.isRequired,
openMenu: React.PropTypes.func.isRequired, openMenu: PropTypes.func.isRequired,
closeMenu: React.PropTypes.func.isRequired, closeMenu: PropTypes.func.isRequired,
showModal: React.PropTypes.func.isRequired, showModal: PropTypes.func.isRequired,
hideModal: React.PropTypes.func.isRequired, hideModal: PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired, tooltip: PropTypes.func.isRequired,
termtip: React.PropTypes.func.isRequired, termtip: PropTypes.func.isRequired,
onWindowResize: React.PropTypes.func.isRequired onWindowResize: PropTypes.func.isRequired
}; };
/** /**

View File

@@ -1,83 +1,68 @@
import React from 'react'; import React from 'react';
import cn from 'classnames';
import SlotSection from './SlotSection'; import SlotSection from './SlotSection';
import HardpointSlot from './HardpointSlot'; import Slot from './Slot';
import { stopCtxPropagation } from '../utils/UtilityFunctions'; import { stopCtxPropagation } from '../utils/UtilityFunctions';
import autoBind from 'auto-bind';
/** /**
* Utility Slot Section * Utility Slot Section
*/ */
export default class UtilitySlotSection extends SlotSection { export default class UtilitySlotSection extends SlotSection {
/** /**
* Constructor * Constructor
* @param {Object} props React Component properties * @param {Object} props React Component properties
* @param {Object} context React Component context
*/ */
constructor(props, context) { constructor(props) {
super(props, context, 'utility', 'utility mounts'); super(props, 'utility mounts');
this._empty = this._empty.bind(this); autoBind(this);
} }
/** /**
* Empty all utility slots and close the menu * Empty all utility slots and close the menu
*/ */
_empty() { _empty() {
this.props.ship.emptyUtility(); this.props.ship.getUtilities().forEach((slot) => slot.reset());
this.props.onChange();
this._close(); this._close();
} }
/** /**
* Mount module in utility slot, replace all if Alt is held * Mount module in utility slot, replace all if Alt is held
* @param {string} group Module Group name * @param {string} type Module type
* @param {string} rating Module Rating * @param {string} rating Module Rating
* @param {string} name Module name
* @param {Synthetic} event Event * @param {Synthetic} event Event
*/ */
_use(group, rating, name, event) { _use(type, rating, event) {
this.props.ship.useUtility(group, rating, name, event.getModifierState('Alt')); const fillAll = event.getModifierState('Alt');
this.props.onChange(); for (const slot of this.props.ship.getUtilities(undefined, true)) {
if (slot.isEmpty() || fillAll) {
slot.setItem(type, '', rating);
}
}
this._close(); this._close();
} }
/**
* Empty all utility slots on right-click
*/
_contextMenu() {
this._empty();
}
/** /**
* Create all HardpointSlots (React component) for the slots * Create all HardpointSlots (React component) for the slots
* @return {Array} Array of HardpointSlots * @return {Array} Array of HardpointSlots
*/ */
_getSlots() { _getSlots() {
let slots = []; let slots = [];
let { ship, currentMenu } = this.props; let { ship, currentMenu, propsToShow, onPropToggle } = this.props;
let hardpoints = ship.hardpoints;
let { originSlot, targetSlot } = this.state; let { originSlot, targetSlot } = this.state;
let availableModules = ship.getAvailableModules();
for (let i = 0, l = hardpoints.length; i < l; i++) { for (let h of ship.getUtilities(undefined, true)) {
let h = hardpoints[i]; slots.push(<Slot
if (h.maxClass === 0) { key={h.object.Slot}
slots.push(<HardpointSlot currentMenu={currentMenu}
key={i} drag={this._drag.bind(this, h)}
maxClass={h.maxClass} dragOver={this._dragOverSlot.bind(this, h)}
availableModules={() => availableModules.getHps(h.maxClass)} drop={this._drop}
onOpen={this._openMenu.bind(this,h)} dropClass={this._dropClass(h, originSlot, targetSlot)}
onSelect={this._selectModule.bind(this, h)} m={h}
selected={currentMenu == h} enabled={h.enabled ? true : false}
drag={this._drag.bind(this, h)} propsToShow={propsToShow}
dragOver={this._dragOverSlot.bind(this, h)} onPropToggle={onPropToggle}
drop={this._drop} />);
dropClass={this._dropClass(h, originSlot, targetSlot)}
enabled={h.enabled}
ship={ship}
m={h.m}
/>);
}
} }
return slots; return slots;
@@ -88,27 +73,35 @@ export default class UtilitySlotSection extends SlotSection {
* @param {Function} translate Translate function * @param {Function} translate Translate function
* @return {React.Component} Section menu * @return {React.Component} Section menu
*/ */
_getSectionMenu(translate) { _getSectionMenu() {
const { translate } = this.context.language;
let _use = this._use; let _use = this._use;
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}> return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul> <ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li> <li className='lc' tabIndex='0' onClick={this._empty}>{translate('empty all')}</li>
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li> <li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('sb')}</div> <div className='select-group cap'>{translate('sb')}</div>
<ul> <ul>
<li className='c' onClick={_use.bind(this, 'sb', 'E', null)}>E</li> <li className='c' tabIndex='0' onClick={_use.bind(this, 'shieldbooster', '5')}>A</li>
<li className='c' onClick={_use.bind(this, 'sb', 'D', null)}>D</li> <li className='c' tabIndex='0' onClick={_use.bind(this, 'shieldbooster', '4')}>B</li>
<li className='c' onClick={_use.bind(this, 'sb', 'C', null)}>C</li> <li className='c' tabIndex='0' onClick={_use.bind(this, 'shieldbooster', '3')}>C</li>
<li className='c' onClick={_use.bind(this, 'sb', 'B', null)}>B</li> <li className='c' tabIndex='0' onClick={_use.bind(this, 'shieldbooster', '2')}>D</li>
<li className='c' onClick={_use.bind(this, 'sb', 'A', null)}>A</li> <li className='c' tabIndex='0' onClick={_use.bind(this, 'shieldbooster', '1')}>E</li>
</ul> </ul>
<div className='select-group cap'>{translate('cm')}</div> <div className='select-group cap'>{translate('hs')}</div>
<ul> <ul>
<li className='lc' onClick={_use.bind(this, 'cm', null, 'Heat Sink Launcher')}>{translate('Heat Sink Launcher')}</li> <li className='lc' tabIndex='0' onClick={_use.bind(this, 'heatsinklauncher', '')}>{translate('Heat Sink Launcher')}</li>
</ul>
<div className='select-group cap'>{translate('ch')}</div>
<ul>
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'chafflauncher', '')}>{translate('Chaff Launcher')}</li>
</ul>
<div className='select-group cap'>{translate('po')}</div>
<ul>
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'pointdefence', '')}>{translate('Point Defence')}</li>
</ul> </ul>
</div>; </div>;
} }
} }

View File

@@ -0,0 +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;
}
}
}

View File

@@ -0,0 +1,104 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import LineChart from '../components/LineChart';
import { moduleReduce } from 'ed-forge/lib/src/helper';
import { chain, keys, mapValues, values } from 'lodash';
const DAMAGE_DEALT_COLORS = ['#FFFFFF', '#FF0000', '#00FF00', '#7777FF', '#FFFF00', '#FF00FF', '#00FFFF', '#777777'];
const PORTION_MAPPINGS = {
'absolute': 'absolutedamageportion',
'explosive': 'explosivedamageportion',
'kinetic': 'kineticdamageportion',
'thermal': 'thermicdamageportion',
};
const MULTS = keys(PORTION_MAPPINGS);
// TODO: help with this in ed-forge
/**
* .
* @param {Object} opponentDefence .
* @returns {Object} .
*/
function defenceToMults(opponentDefence) {
return chain(opponentDefence)
.pick(MULTS)
.mapKeys((v, k) => PORTION_MAPPINGS[k])
.mapValues((resistanceProfile) => resistanceProfile.damageMultiplier)
.value();
}
/**
* Weapon damage chart
*/
export default class WeaponDamageChart extends TranslatedComponent {
static propTypes = {
code: PropTypes.string.isRequired,
ship: PropTypes.object.isRequired,
opponentDefence: PropTypes.object.isRequired,
engagementRange: PropTypes.number.isRequired,
};
/**
* Render damage dealt
* @return {React.Component} contents
*/
render() {
const { language } = this.context;
const { translate } = language;
const { code, ship, opponentDefence, engagementRange } = this.props;
const hardpoints = ship.getHardpoints();
const hardpointsMap = chain(hardpoints)
.map((m) => [m.getSlot(), m])
.fromPairs()
.value();
const mults = defenceToMults(opponentDefence);
const cb = (range) => {
return mapValues(
hardpointsMap,
(m) => {
const sdps = m.get('sustaineddamagepersecond', true);
const resistanceMul = chain(mults)
.toPairs()
.map((pair) => {
const [k, mul] = pair;
return m.get(k, true) * mul;
})
.sum()
.value();
const falloff = m.get('damagefalloffrange', true);
const rangeMul = Math.min(1, Math.max(0,
1 - (range - falloff) / (m.get('maximumrange', true) - falloff)
));
return sdps * resistanceMul * rangeMul;
}
);
};
return (
<div>
<LineChart
xMin={0}
xMax={moduleReduce(
hardpoints, 'maximumrange', true, (a, v) => Math.max(a, v), 1000,
)}
yMin={0}
// Factor in highest damage multiplier to get a safe upper bound
yMax={Math.max(1, ...values(mults)) * moduleReduce(
hardpoints, 'sustaineddamagepersecond', true, (a, v) => Math.max(a, v), 0,
)}
xLabel={translate('range')}
xUnit={translate('m')}
yLabel={translate('sustaineddamagepersecond')}
series={hardpoints.map((m) => m.getSlot())}
xMark={engagementRange}
colors={DAMAGE_DEALT_COLORS}
func={cb}
points={200}
code={code}
/>
</div>
);
}
}

View File

@@ -5,7 +5,10 @@ import * as ES from './es';
import * as FR from './fr'; import * as FR from './fr';
import * as IT from './it'; import * as IT from './it';
import * as RU from './ru'; import * as RU from './ru';
import d3 from 'd3'; import * as PL from './pl';
import * as PT from './pt';
import * as CN from './cn';
import * as d3 from 'd3';
let fallbackTerms = EN.terms; let fallbackTerms = EN.terms;
@@ -23,47 +26,60 @@ export function getLanguage(langCode) {
case 'fr': lang = FR; break; case 'fr': lang = FR; break;
case 'it': lang = IT; break; case 'it': lang = IT; break;
case 'ru': lang = RU; break; case 'ru': lang = RU; break;
case 'pl': lang = PL; break;
case 'pt': lang = PT; break;
case 'cn': lang = CN; break;
default: default:
lang = EN; lang = EN;
} }
let currentTerms = lang.terms; let currentTerms = lang.terms;
let d3Locale = d3.locale(lang.formats); let d3Locale = d3.formatLocale(lang.formats);
let gen = d3Locale.numberFormat('n'); let gen = d3Locale.format('');
const round = function(x, n) { const ten_n = Math.pow(10,n); return Math.round(x * ten_n) / ten_n; };
if(lang === EN) { if(lang === EN) {
translate = (t) => { return currentTerms[t] || t; }; translate = (t, x) => { return currentTerms[t + '_' + x] || currentTerms[t] || t; };
} else { } else {
translate = (t) => { return currentTerms[t] || fallbackTerms[t] || t; }; translate = (t, x) => { return currentTerms[t + '_' + x] || currentTerms[t] || fallbackTerms[t + '_' + x] || fallbackTerms[t] || t; };
} }
return { return {
formats: { formats: {
gen, // General number format (.e.g 1,001,001.1234) gen, // General number format (.e.g 1,001,001.1234)
int: d3Locale.numberFormat(',.0f'), // Fixed to 0 decimal places (.e.g 1,001) int: d3Locale.format(',.0f'), // Fixed to 0 decimal places (.e.g 1,001)
f2: d3Locale.numberFormat(',.2f'), // Fixed to 2 decimal places (.e.g 1,001.10) f1: d3Locale.format(',.1f'), // Fixed to 1 decimal place (.e.g 1,001.1)
s2: d3Locale.numberFormat('.2s'), // SI Format to 2 decimal places (.e.g 1.1k) f2: d3Locale.format(',.2f'), // Fixed to 2 decimal places (.e.g 1,001.10)
pct: d3Locale.numberFormat('.2%'), // % to 2 decimal places (.e.g 5.40%) s2: d3Locale.format('.2s'), // SI Format to 2 decimal places (.e.g 1.1k)
pct1: d3Locale.numberFormat('.1%'), // % to 1 decimal places (.e.g 5.4%) pct: d3Locale.format('.2%'), // % to 2 decimal places (.e.g 5.40%)
r2: d3Locale.numberFormat('.2r'), // Rounded to 2 significant numbers (.e.g 512 => 510, 4.122 => 4.1) pct1: d3Locale.format('.1%'), // % to 1 decimal places (.e.g 5.4%)
rPct: d3.format('%'), // % to 0 decimal places (.e.g 5%) r1: d3Locale.format('.1r'), // Rounded to 1 significant number (.e.g 512 => 500, 4.122 => 4)
round: (d) => gen(d3.round(d, 2)), // Rounded to 0-2 decimal places (.e.g 5.12, 4.1) r2: d3Locale.format('.2r'), // Rounded to 2 significant numbers (.e.g 512 => 510, 4.122 => 4.1)
rPct: d3Locale.format('.0%'), // % to 0 decimal places (.e.g 5%)
round1: (d) => gen(round(d, 1)), // Round to 0-1 decimal places (e.g. 5.1, 4)
round: (d) => gen(round(d, 2)), // Rounded to 0-2 decimal places (.e.g 5.12, 4.1)
time: (d) => (d < 0 ? '-' : '') + Math.floor(Math.abs(d) / 60) + ':' + ('00' + Math.floor(Math.abs(d) % 60)).substr(-2, 2) time: (d) => (d < 0 ? '-' : '') + Math.floor(Math.abs(d) / 60) + ':' + ('00' + Math.floor(Math.abs(d) % 60)).substr(-2, 2)
}, },
translate, translate,
units: { units: {
CR: <u> {translate('CR')}</u>, // Credits ang: '°', // Angle
kg: <u> {translate('kg')}</u>, // Kilograms CR: <u>{translate('CR')}</u>, // Credits
kgs: <u> {translate('kg/s')}</u>, // Kilograms per second kg: <u>{translate('kg')}</u>, // Kilograms
km: <u> {translate('km')}</u>, // Kilometers kgs: <u>{translate('kg/s')}</u>, // Kilograms per second
Ls: <u> {translate('Ls')}</u>, // Light Seconds km: <u>{translate('km')}</u>, // Kilometers
LY: <u> {translate('LY')}</u>, // Light Years Ls: <u>{translate('Ls')}</u>, // Light Seconds
MJ: <u> {translate('MJ')}</u>, // Mega Joules LY: <u>{translate('LY')}</u>, // Light Years
'm/s': <u> {translate('m/s')}</u>, // Meters per second m: <u>{translate('m')}</u>, // Meters
MW: <u> {translate('MW')}</u>, // Mega Watts (same as Mega Joules per second) MJ: <u>{translate('MJ')}</u>, // Mega Joules
'm/s': <u>{translate('m/s')}</u>, // Meters per second
'°/s': <u>{translate('°/s')}</u>, // Degrees per second
MW: <u>{translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
mps: <u>{translate('m/s')}</u>, // Metres per second
pct: '%', // Percent
ps: <u>{translate('/s')}</u>, // per second ps: <u>{translate('/s')}</u>, // per second
pm: <u>{translate('/min')}</u>, // per minute pm: <u>{translate('/min')}</u>, // per minute
T: <u> {translate('T')}</u>, // Metric Tons s: <u>{translate('secs')}</u>, // Seconds
T: <u>{translate('T')}</u>, // Metric Tons
} }
}; };
} }
@@ -78,5 +94,8 @@ export const Languages = {
it: 'Italiano', it: 'Italiano',
es: 'Español', es: 'Español',
fr: 'Français', fr: 'Français',
ru: 'ру́сский' ru: 'ру́сский',
pl: 'polski',
pt: 'português',
cn: '中文'
}; };

16
src/app/i18n/cn.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: ['AM', 'PM'],
days: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
shortDays: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
shortMonths: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
};
export { default as terms } from './cn.json';

405
src/app/i18n/cn.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,166 +1,16 @@
export const formats = { export const formats = {
decimal: ',', decimal: ',',
thousands: '.', thousands: '.',
grouping: [3], grouping: [3],
currency: ['', ' €'], currency: ['', ' €'],
dateTime: '%A, der %e. %B %Y, %X', dateTime: '%A, der %e. %B %Y, %X',
date: '%d.%m.%Y', date: '%d.%m.%Y',
time: '%H:%M:%S', time: '%H:%M:%S',
periods: ['AM', 'PM'], // unused periods: ['AM', 'PM'], // unused
days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'], days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
shortDays: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'], shortDays: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
shortMonths: ['Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'] shortMonths: ['Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']
}; };
export const terms = { export { default as terms } from './de.json';
// Phrases
PHRASE_BACKUP_DESC: 'Export aller Coriolis-Daten, um sie zu sichern oder um sie zu einem anderen Browser/Gerät zu übertragen.', // Backup of all Coriolis data to save or transfer to another browser/device
PHRASE_CONFIRMATION: 'Sind Sie sicher?', // Are You Sure?
PHRASE_EXPORT_DESC: 'Ein detaillierter JSON-Export Ihrer Konfiguration für die Verwendung in anderen Websites und Tools', // A detailed JSON export of your build for use in other sites and tools
PHRASE_FASTEST_RANGE: 'aufeinanderfolgende maximale Reichweite/Sprünge', // Consecutive max range jumps
PHRASE_IMPORT: 'JSON hier einfügen oder importieren', // Paste JSON or import here
PHRASE_LADEN: 'Schiffsmasse + Treibstoff + Fracht', // Ship Mass + Fuel + Cargo
PHRASE_NO_BUILDS: 'Keine Konfigurationen zum Vergleich ausgewählt!', // No builds added to comparison!
PHRASE_NO_RETROCH: 'Keine Umrüständerungen', // No Retrofitting changes
PHRASE_SELECT_BUILDS: 'Ausstattung zum Vergleich auswählen', // Select Builds to Compare
PHRASE_SG_RECHARGE: 'Zeit von 50% bis 100% der Ladung', // Time from 50% to 100% Charge
PHRASE_SG_RECOVER: 'Erneuerung (zu 50%) nach Zusammenbruch', // Recovery (to 50%) after collapse
PHRASE_UNLADEN: 'Schiffsmasse ohne Treibstoff und Fracht', // Ship Mass excluding Fuel and Cargo
PHRASE_UPDATE_RDY: 'Update verfügbar! Klicken zum Aktualisieren', // Update Available! Click to Refresh
// Units / Metrics
LY: 'Lj', // Light Years
T: 't', // Tons (Metric Ton - 1000kg)
// Sizes
S: 'K', // Small Hardpoint (single Character)
L: 'G', // Large Hardpoint size (single character)
H: 'R', // Huge Hardpoint size (single character)
U: 'W', // Utility Hardpoint size (single character) - Kill warrant scanner, etc
small: 'klein', // Small ship size
medium: 'mittel', // Medium ship size
large: 'groß', // Large Ship Size
// Terms
about: 'über', // Link to about page / about Coriolis.io
action: 'Aktion',
added: 'hinzugefügt',
ammo: 'Munition', // Ammunition
armour: 'Panzerung',
available: 'verfügbar', // Available options
backup: 'Sicherungsdatei',
base: 'Basis', // Base speed, boost, etc - Base ship stats
bays: 'Lagerraum',
bins: 'Behälter', // Number of Mining Refinery bins
build: 'Ausstattung', // Shorthand for the build/configuration/design name
'build name': 'Ausstattungsname', // Ship build/configuration/design name
builds: 'Ausstattungen', // Ship build/configuration/design names
buy: 'kaufen',
cancel: 'abbrechen',
cargo: 'Fracht',
cells: 'Zellen', // Number of cells in a shield cell bank
close: 'schließen',
compare: 'vergleichen',
'compare all': 'alles vergleichen',
comparison: 'Vergleich',
comparisons: 'Vergleiche',
cost: 'Preis', // Cost / price of a module or price of a ship
costs: 'Kosten', // Costs / prices of a modules or prices of ships
create: 'erstellen',
'create new': 'neu erstellen',
credits: 'Credits',
damage: 'Schaden',
'damage per second': 'Schaden pro Sekunde',
delete: 'löschen',
'delete all': 'alles löschen',
dep: 'ausg', // Weapons/Hardpoints Deployed abbreviation
deployed: 'ausgefahren', // Weapons/Hardpoints Deployed
'detailed export': 'detailierter Export',
disabled: 'deaktiviert',
discount: 'Rabatt',
'edit data': 'bearbeiten',
efficiency: 'Effizienz', // Power Plant efficiency
empty: 'leer',
'empty all': 'alles entfernen',
ENG: 'ANT', // Abbreviation - Engine recharge rate for power distributor
'Enter Name': 'Namen eingeben',
Explorer: 'Forscher',
export: 'Export',
'fastest range': 'maximale Reichweite', // Fastet totaljump range - sum of succesive jumps
forum: 'Forum',
fuel: 'Treibstoff',
'fuel level': 'Tankfüllstand', // Percent of fuel (T) in the tank
'full tank': 'Tank voll',
hardpoints: 'Waffenaufhängungen',
hull: 'Rumpf', // Ships hull
import: 'importieren',
insurance: 'Versicherung',
'internal compartments': 'Innenbereich',
jump: 'Sprung', // Single jump range
'jump range': 'Sprungreichweite',
jumps: 'Sprünge',
laden: 'beladen',
language: 'Sprache',
maneuverability: 'Manövrierbarkeit',
manufacturer: 'Hersteller',
mass: 'Masse',
'mass lock factor': 'Massensperrefaktor',
'max mass': 'maximale Masse',
MLF: 'MSF', // Mass Lock Factor Abbreviation
module: 'Modul',
modules: 'Module',
'net cost': 'Nettokosten',
no: 'Nein',
'none created': 'nicht erstellt',
ok: 'OK',
'optimal mass': 'optimale Masse', // Lowest weight / best weight for jump distance, etc
optimize: 'optimieren',
pen: 'Durchdr.', // Armour peneration abbreviation
permalink: 'Permanentlink',
power: 'Energie', // Power = Energy / second. Power generated by the power plant, or power consumed (MW / Mega Watts). Used in the power plant section
pri: 'Prio', // Priority abbreviation for power management
proceed: 'fortfahren',
qty: 'Menge', // Quantity abbreviation
range: 'Reichweite',
rate: 'Rate',
recharge: 'aufladen', // Shield Recharge time from 50% -> 100%
recovery: 'Erneuerung', // Shield recovery time (after losing shields/turning on -> 50%)
'refuel time': 'Auftankzeit', // Time to refuel the tank when scooping
reload: 'aktualisieren', // Reload weapon/hardpoint
'reload costs': 'Nachladekosten',
rename: 'umbenennen',
repair: 'reparieren',
reset: 'zurücksetzen',
ret: 'eing', // Retracted abbreviation
retracted: 'eingefahren', // Weapons/Hardpoints retracted
'retrofit costs': 'Änderungskosten', // The cost difference when upgrading / downgrading a component
'retrofit from': 'nachrüsten von', // Retrofit from Build A against build B
ROF: 'Kad', // Rate of Fire abbreviation
roles: 'Rollen', // Commander/Ship build roles - e.g. Trader, Bounty-Hunter, Explorer, etc
save: 'speichern',
sell: 'verkaufen',
settings: 'Einstellungen', // Coriolis application settings
shields: 'Schilde',
ship: 'Schiff',
ships: 'Schiffe',
shortened: 'gekürzt', // Standard/Stock build of a ship when purchased new
size: 'Größe',
skip: 'überspringen', // Skip past something / ignore it
speed: 'Geschwindigkeit',
standard: 'Grundausstattung', // Standard / Common modules (FSD, power plant, life support, etc)
Stock: 'Standard', // Thermal-load abbreviation
strength: 'Stärke', // Strength in reference to Shield Strength
subtotal: 'Zwischensumme',
time: 'Dauer', // time it takes to complete something
tooltips: 'Tooltips', // Tooltips setting - show/hide
total: 'gesamt',
'total range': 'Gesamtbereich',
Trader: 'Händler', // Trader role
type: 'Typ',
'unit cost': 'Kosten pro Einheit',
unladen: 'unbeladen', // No cargo or fuel
'utility mounts': 'Werkzeug-Steckplätze',
WEP: 'WAF', // Abbreviation - Weapon recharge rate for power distributor
yes: 'ja'
};

450
src/app/i18n/de.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -13,65 +13,4 @@ export const formats = {
shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
}; };
export const terms = { export { default as terms } from './en.json';
PHRASE_ALT_ALL: 'Alt + Click to fill all slots',
PHRASE_BACKUP_DESC: 'Backup of all Coriolis data to save or transfer to another browser/device',
PHRASE_CONFIRMATION: 'Are you sure?',
PHRASE_EXPORT_DESC: 'A detailed JSON export of your build for use in other sites and tools',
PHRASE_FASTEST_RANGE: 'Consecutive max range jumps',
PHRASE_IMPORT: 'Paste JSON or import here',
PHRASE_LADEN: 'Ship mass + fuel + cargo',
PHRASE_NO_BUILDS: 'No builds added to comparison!',
PHRASE_NO_RETROCH: 'No Retrofitting changes',
PHRASE_SELECT_BUILDS: 'Select builds to compare',
PHRASE_SG_RECHARGE: 'Time from 50% to 100% charge',
PHRASE_SG_RECOVER: 'Recovery (to 50%) after collapse',
PHRASE_UNLADEN: 'Ship mass excluding fuel and cargo',
PHRASE_UPDATE_RDY: 'Update Available! Click to refresh',
// Other languages fallback to these values
// Only Translate to other languages if the name is different in-game
am: 'Auto Field-Maintenance Unit',
bh: 'Bulkheads',
bl: 'Beam Laser',
bsg: 'Bi-Weave Shield Generator',
c: 'Cannon',
cc: 'Collector Limpet Controller',
cm: 'Countermeasure',
cr: 'Cargo Rack',
cs: 'Cargo Scanner',
dc: 'Docking Computer',
fc: 'Fragment Cannon',
fi: 'FSD Interdictor',
fs: 'Fuel Scoop',
fsd: 'Frame Shift Drive',
ft: 'Fuel Tank',
fx: 'Fuel Transfer Limpet Controller',
hb: 'Hatch Breaker Limpet Controller',
hr: 'Hull Reinforcement Package',
kw: 'Kill Warrant Scanner',
ls: 'Life Support',
mc: 'Multi-cannon',
ml: 'Mining Laser',
mr: 'Missile Rack',
nl: 'Mine Launcher',
pa: 'Plasma Accelerator',
pas: 'Planetary Approach Suite',
pc: 'Prospector Limpet Controller',
pd: 'power distributor',
pl: 'Pulse Laser',
pp: 'Power Plant',
psg: 'Prismatic Shield Generator',
pv: 'Planetary Vehicle Hangar',
rf: 'Refinery',
rg: 'Rail Gun',
s: 'Sensors',
sb: 'Shield Booster',
sc: 'Scanner',
scb: 'Shield Cell Bank',
sg: 'Shield Generator',
t: 'thrusters',
tp: 'Torpedo Pylon',
ul: 'Burst Laser',
ws: 'Frame Shift Wake Scanner'
};

366
src/app/i18n/en.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -13,196 +13,4 @@ export const formats = {
shortMonths: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'] shortMonths: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic']
}; };
export const terms = { export { default as terms } from './es.json';
'PHRASE_EXPORT_DESC': 'Una detallada exportaci\u00f3n JSON de tu construcci\u00f3n para usarlo en otros sitios web y herramientas',
'A-Rated': 'Calidad-A',
'about': 'Acerca',
'action': 'Acci\u00f3n',
'added': 'A\u00f1adido',
'Advanced Discovery Scanner': 'Esc\u00e1ner de exploraci\u00f3n avanzado',
maneuverability: 'Maniobrabilidad',
'alpha': 'Alfa',
'ammo': 'Munici\u00f3n',
'PHRASE_CONFIRMATION': '\u00bfEst\u00e1s seguro?',
'armour': 'Blindaje',
'am': 'Unidad de auto-reparaciones',
'available': 'Disponible',
'backup': 'Reserva',
'Basic Discovery Scanner': 'Esc\u00e1ner de exploraci\u00f3n b\u00e1sico',
'bl': 'L\u00e1ser de haz',
'bins': 'contenedores',
'boost': 'incrementar',
'build': 'Construcci\u00f3n',
'build name': 'Nombre de la construcci\u00f3n',
'builds': 'Construcciones',
'bh': 'mamparos',
'ul': 'Laser de r\u00e1fagas',
'buy': 'Comprar',
'cancel': 'Cancelar',
'c': 'Ca\u00f1\u00f3n',
'capital': 'capital',
'cargo': 'Carga',
'Cargo Hatch': 'Compuerta de carga',
'cr': 'Compartimento de carga',
'cs': 'Esc\u00e1ner de carga',
'cells': 'celdas',
'Chaff Launcher': 'Lanzador de birutas',
'close': 'Cerrar',
'cc': 'Controlador de Drones de Recogida',
'compare': 'Comparar',
'compare all': 'comparar todas',
'comparison': 'Comparativa',
'comparisons': 'Comparativas',
'component': 'Componente',
'cost': 'Coste',
'costs': 'Costes',
'cm': 'Contramedidas',
'create': 'Crear',
'create new': 'Crear nuevo',
'credits': 'Cr\u00e9ditos',
'damage': 'Da\u00f1o',
'delete': 'Borrar',
'delete all': 'Borrar todo',
'dep': 'desp',
'deployed': 'Desplegado',
'detailed export': 'Exportacion detallada',
'Detailed Surface Scanner': 'Escaner de exploraci\u00f3n detallada',
'disabled': 'Desactivado',
'discount': 'Descuento',
'dc': 'Ordenador de aterrizaje',
'done': 'Hecho',
'DPS': 'DPS (Da\u00f1o Por Segundo)',
'edit data': 'Editar datos',
'efficiency': 'Eficiencia',
'Electronic Countermeasure': 'Contramedidas electr\u00f3nicas',
'empty': 'Vac\u00edo',
'ENG': 'MOT',
'enter name': 'Introduce el nombre',
'export': 'exportar',
'fixed': 'fijo',
'forum': 'Foro',
'fc': 'Ca\u00f1\u00f3n de fragmentaci\u00f3n',
'fsd': 'Motor de salto',
'ws': 'Esc\u00e1ner de Salto',
'fi': 'Interdictor FSD',
'fuel': 'Combustible',
'fs': 'Recolector de Combustible',
'ft': 'Tanque de combustible',
'fx': 'Sistema de Transferencia de Combustilble',
'full tank': 'Tanque lleno',
'Gimballed': 'Card\u00e1n',
'H': 'E',
'hardpoints': 'Montura de armas',
'hb': 'Controlador de Apertura de Bah\u00eda de Carga',
'Heat Sink Launcher': 'Eyector de Acumulador de Calor',
'huge': 'enorme',
'hull': 'Casco',
'hr': 'Sistema de Casco Reforzado',
'import': 'Importar',
'import all': 'Importar todo',
'insurance': 'Seguro',
'Intermediate Discovery Scanner': 'Esc\u00e1ner de exploraci\u00f3n media',
'internal compartments': 'Compartimentos internos',
'jump range': 'Rango de salto',
'jumps': 'Saltos',
'kw': 'Esc\u00e1ner Detector de Recompensas',
'L': 'G',
'laden': 'Cargada',
'language': 'Idioma',
'large': 'Grande',
'ls': 'Soporte vital',
'Lightweight Alloy': 'Aleaci\u00f3n ligera',
'limpets': 'Drones',
'lock factor': 'factor de bloqueo',
'mass': 'Masa',
'max': 'm\u00e1x',
'max mass': 'Masa m\u00e1xima',
'medium': 'medio',
'Military Grade Composite': 'Blindaje Militar',
'nl': 'Lanzaminas',
'Mining Lance': 'Lanza de miner\u00eda',
'ml': 'L\u00e1ser de miner\u00eda',
'Mirrored Surface Composite': 'Blindaje Reflectante',
'mr': 'Bah\u00eda de Misiles',
'mc': 'Ca\u00f1\u00f3n m\u00faltiple',
'net cost': 'Coste neto',
'PHRASE_NO_BUILDS': '\u00a1No se a\u00f1adieron plantillas para comparaci\u00f3n!',
'PHRASE_NO_RETROCH': 'No hay cambios en los ajutes',
'none': 'Nada',
'none created': 'Nada creado',
'off': 'apagado',
'on': 'encendido',
'optimal': '\u00f3ptimo',
'optimal mass': 'masa \u00f3ptima',
'optimize mass': 'optimizar masa',
'overwrite': 'Sobreescribir',
'PHRASE_IMPORT': 'Pega el JSON o imp\u00f3rtalo aqu\u00ed',
'penetration': 'penetraci\u00f3n',
'permalink': 'enlace permanente',
'pa': 'Acelerador de Plasma',
'Point Defence': 'Punto de Defensa',
'power': 'energ\u00eda',
'pd': 'distribuidor de energ\u00eda',
'pp': 'Planta de Energ\u00eda',
'priority': 'prioridad',
'proceed': 'Proceder',
'pc': 'Controlador de drones de prospecci\u00f3n',
'pl': 'L\u00e1ser de Pulso',
'PWR': 'POT',
'rg': 'Ca\u00f1\u00f3n de Riel',
'range': 'rango',
'rate': 'ratio',
'Reactive Surface Composite': 'Blindaje Reactivo',
'recharge': 'recargar',
'rf': 'Refineria',
'refuel time': 'Tiempo para repostar',
'Reinforced Alloy': 'Armadura reforzada',
'reload': 'Recargar',
'rename': 'Renombrar',
'repair': 'Reparar',
'reset': 'Reiniciar',
'ret': 'PLE',
'retracted': 'plegadas',
'retrofit costs': 'costes de equipamiento',
'retrofit from': 'equipamiento desde',
'ROF': 'RDF',
'S': 'P',
'save': 'guardar',
'sc': 'sc\u00e1ner',
'PHRASE_SELECT_BUILDS': 'Selecciona equipamientos para comparar',
'sell': 'Vender',
's': 'Sensores',
'settings': 'Configuraci\u00f3n',
'sb': 'Potenciador de Escudos',
'scb': 'C\u00e9lula de Energ\u00eda de Escudos',
'sg': 'Generador de escudos',
'shields': 'Escudos',
'ship': 'Nave ',
'ships': 'Naves',
'shortened': 'Abreviado',
'size': 'Tama\u00f1o',
'skip': 'omitir',
'small': 'Peque\u00f1o',
'speed': 'velocidad',
'standard': 'est\u00e1ndar',
'Standard Docking Computer': 'Computador de Atraque Est\u00e1ndar',
'Stock': 'De serie',
'SYS': 'SIS',
'T_LOAD': 'c-t\u00e9rmica',
't': 'Propulsores',
'time': 'Tiempo',
'tp': 'Anclaje de torpedo',
'total': 'Total',
'total range': 'Rango total',
'turret': 'torreta',
'type': 'Tipo',
'unladen': 'Sin carga',
'PHRASE_UPDATE_RDY': 'Actualizacion disponible! Haz click para recargar',
'URL': 'Enlace',
'utility': 'utilidad',
'utility mounts': 'monturas de utilidad',
'version': 'Versi\u00f3n',
'WEP': 'ARM',
'yes': 'si',
'PHRASE_BACKUP_DESC': 'Copia de seguridad de todos los datos de Coriolis para guardarlos o transferirlos a otro navegador\/dispositivo'
};

193
src/app/i18n/es.json Normal file
View File

@@ -0,0 +1,193 @@
{
"PHRASE_EXPORT_DESC": "Una detallada exportación JSON de tu construcción para usarlo en otros sitios web y herramientas",
"A-Rated": "Calidad-A",
"about": "Acerca",
"action": "Acción",
"added": "Añadido",
"Advanced Discovery Scanner": "Escáner de exploración avanzado",
"maneuverability": "Maniobrabilidad",
"alpha": "Alfa",
"ammo": "Munición",
"PHRASE_CONFIRMATION": "¿Estás seguro?",
"armour": "Blindaje",
"am": "Unidad de auto-reparaciones",
"available": "Disponible",
"backup": "Reserva",
"Basic Discovery Scanner": "Escáner de exploración básico",
"bl": "Láser de haz",
"bins": "contenedores",
"boost": "incrementar",
"build": "Construcción",
"build name": "Nombre de la construcción",
"builds": "Construcciones",
"bh": "mamparos",
"ul": "Laser de ráfagas",
"buy": "Comprar",
"cancel": "Cancelar",
"c": "Cañón",
"capital": "capital",
"cargo": "Carga",
"Cargo Hatch": "Compuerta de carga",
"cr": "Compartimento de carga",
"cs": "Escáner de carga",
"cells": "celdas",
"Chaff Launcher": "Lanzador de virutas",
"close": "Cerrar",
"cc": "Controlador de Drones de Recogida",
"compare": "Comparar",
"compare all": "comparar todas",
"comparison": "Comparativa",
"comparisons": "Comparativas",
"component": "Componente",
"cost": "Coste",
"costs": "Costes",
"cm": "Contramedidas",
"create": "Crear",
"create new": "Crear nuevo",
"credits": "Créditos",
"damage": "Daño",
"delete": "Borrar",
"delete all": "Borrar todo",
"dep": "desp",
"deployed": "Desplegado",
"detailed export": "Exportacion detallada",
"Detailed Surface Scanner": "Escaner de exploración detallada",
"disabled": "Desactivado",
"discount": "Descuento",
"dc": "Ordenador de aterrizaje",
"done": "Hecho",
"DPS": "DPS (Daño Por Segundo)",
"edit data": "Editar datos",
"efficiency": "Eficiencia",
"Electronic Countermeasure": "Contramedidas electrónicas",
"empty": "Vacío",
"ENG": "MOT",
"enter name": "Introduce el nombre",
"export": "exportar",
"fixed": "fijo",
"forum": "Foro",
"fc": "Cañón de fragmentación",
"fsd": "Motor de salto",
"ws": "Escáner de Salto",
"fi": "Interdictor FSD",
"fuel": "Combustible",
"fs": "Recolector de Combustible",
"ft": "Tanque de combustible",
"fx": "Sistema de Transferencia de Combustilble",
"full tank": "Tanque lleno",
"Gimballed": "Cardán",
"H": "E",
"hardpoints": "Montura de armas",
"hb": "Controlador de Apertura de Bahía de Carga",
"Heat Sink Launcher": "Eyector de Acumulador de Calor",
"huge": "enorme",
"hull": "Casco",
"hr": "Sistema de Casco Reforzado",
"import": "Importar",
"import all": "Importar todo",
"insurance": "Seguro",
"Intermediate Discovery Scanner": "Escáner de exploración media",
"internal compartments": "Compartimentos internos",
"jump range": "Rango de salto",
"jumps": "Saltos",
"kw": "Escáner Detector de Recompensas",
"L": "G",
"laden": "Cargada",
"language": "Idioma",
"large": "Grande",
"ls": "Soporte vital",
"Lightweight Alloy": "Aleación ligera",
"limpets": "Drones",
"lock factor": "factor de bloqueo",
"mass": "Masa",
"max": "máx",
"max mass": "Masa máxima",
"medium": "medio",
"Military Grade Composite": "Blindaje Militar",
"nl": "Lanzaminas",
"Mining Lance": "Lanza de minería",
"ml": "Láser de minería",
"Mirrored Surface Composite": "Blindaje Reflectante",
"mr": "Bahía de Misiles",
"mc": "Cañón múltiple",
"net cost": "Coste neto",
"PHRASE_NO_BUILDS": "¡No se añadieron plantillas para comparación!",
"PHRASE_NO_RETROCH": "No hay cambios en los ajutes",
"none": "Nada",
"none created": "Nada creado",
"off": "apagado",
"on": "encendido",
"optimal": "óptimo",
"optimal mass": "masa óptima",
"optimize mass": "optimizar masa",
"overwrite": "Sobreescribir",
"PHRASE_IMPORT": "Pega el JSON o impórtalo aquí",
"penetration": "penetración",
"permalink": "enlace permanente",
"pa": "Acelerador de Plasma",
"Point Defence": "Punto de Defensa",
"power": "energía",
"pd": "distribuidor de energía",
"pp": "Planta de Energía",
"priority": "prioridad",
"proceed": "Proceder",
"pc": "Controlador de drones de prospección",
"pl": "Láser de Pulso",
"PWR": "POT",
"rg": "Cañón de Riel",
"range": "rango",
"rate": "ratio",
"Reactive Surface Composite": "Blindaje Reactivo",
"recharge": "recargar",
"rf": "Refineria",
"refuel time": "Tiempo para repostar",
"Reinforced Alloy": "Armadura reforzada",
"reload": "Recargar",
"rename": "Renombrar",
"repair": "Reparar",
"reset": "Reiniciar",
"ret": "PLE",
"retracted": "plegadas",
"retrofit costs": "costes de equipamiento",
"retrofit from": "equipamiento desde",
"ROF": "RDF",
"S": "P",
"save": "guardar",
"sc": "scáner",
"PHRASE_SELECT_BUILDS": "Selecciona equipamientos para comparar",
"sell": "Vender",
"s": "Sensores",
"settings": "Configuración",
"sb": "Potenciador de Escudos",
"scb": "Célula de Energía de Escudos",
"sg": "Generador de escudos",
"shields": "Escudos",
"ship": "Nave ",
"ships": "Naves",
"shortened": "Abreviado",
"size": "Tamaño",
"skip": "omitir",
"small": "Pequeño",
"speed": "velocidad",
"standard": "estándar",
"Standard Docking Computer": "Computador de Atraque Estándar",
"Stock": "De serie",
"SYS": "SIS",
"T_LOAD": "c-térmica",
"t": "Propulsores",
"time": "Tiempo",
"tp": "Anclaje de torpedo",
"total": "Total",
"total range": "Rango total",
"turret": "torreta",
"type": "Tipo",
"unladen": "Sin carga",
"PHRASE_UPDATE_RDY": "Actualizacion disponible! Haz click para recargar",
"URL": "Enlace",
"utility": "utilidad",
"utility mounts": "monturas de utilidad",
"version": "Versión",
"WEP": "ARM",
"yes": "si",
"PHRASE_BACKUP_DESC": "Copia de seguridad de todos los datos de Coriolis para guardarlos o transferirlos a otro navegador/dispositivo"
}

View File

@@ -13,143 +13,4 @@ export const formats = {
shortMonths: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'] shortMonths: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.']
}; };
export const terms = { export { default as terms } from './fr.json';
// Phrases
PHRASE_BACKUP_DESC: 'Copie des données Coriolis pour l\'utilisation dans d\'autres sites et outils', // Backup of all Coriolis data to save or transfer to another browser/device
PHRASE_CONFIRMATION: 'Êtes-vous sûr?', // Are You Sure?
PHRASE_EXPORT_DESC: 'Un export détaillé en JSON de votre configuration pour l\'utilisation dans d\'autres sites et outils', // A detailed JSON export of your build for use in other sites and tools
PHRASE_FASTEST_RANGE: 'Portée maximale en cas de sauts successifs', // Consecutive max range jumps
PHRASE_IMPORT: 'Coller JSON ou importer ici', // Paste JSON or import here
PHRASE_LADEN: 'Masse du Vaisseau + Carburant + Cargo', // Ship Mass + Fuel + Cargo
PHRASE_NO_BUILDS: 'Aucune configuration ajoutée pour la comparaison', // No builds added to comparison!
PHRASE_NO_RETROCH: 'Aucun changement de configuration', // No Retrofitting changes
PHRASE_SELECT_BUILDS: 'Sélectionner les configurations à comparer', // Select Builds to Compare
PHRASE_SG_RECHARGE: 'Temps de charge de 50% à 100 %', // Time from 50% to 100% Charge
PHRASE_SG_RECOVER: 'Temps de redémarrage après perte du bouclier', // Recovery (to 50%) after collapse
PHRASE_UNLADEN: 'Masse du Vaisseau hors Carburant et Cargo', // Ship Mass excluding Fuel and Cargo
PHRASE_UPDATE_RDY: 'Mise à jour disponible ! Cliquez pour rafraichir', // Update Available! Click to Refresh
// Units / Metrics
Ls: 'SL', // Light seconds
LY: 'AL', // Light Years
// Sizes
L: 'G', // Large Hardpoint size (single character)
large: 'grand', // Large Ship Size
medium: 'moyen', // Medium ship size
S: 'P', // Small Hardpoint (single Character)
small: 'petit', // Small ship size
// Terms
about: 'à propos', // Link to about page / about Coriolis.io
added: 'ajouté',
ammo: 'munition', // Ammunition
armour: 'Armure',
available: 'Disponibilité', // Available options
backup: 'sauvegarde',
bays: 'baies',
bins: 'bacs', // Number of Mining Refinery bins
build: 'Configuration', // Shorthand for the build/configuration/design name
'build name': 'Nom de la configuration', // Ship build/configuration/design name
builds: 'Configurations', // Ship build/configuration/design names
buy: 'Acheter',
cancel: 'Annuler',
cargo: 'Soute',
cells: 'Cellule', // Number of cells in a shield cell bank
close: 'fermer',
compare: 'comparer',
'compare all': 'tout comparer',
comparison: 'comparaison',
comparisons: 'comparaisons',
cost: 'coût', // Cost / price of a module or price of a ship
costs: 'coûts', // Costs / prices of a modules or prices of ships
create: 'Créer',
'create new': 'Créer nouveau',
credits: 'crédits',
damage: 'dégât',
'damage per second': 'dégât par seconde',
delete: 'supprimer',
'delete all': 'tout supprimer',
dep: 'depl', // Weapons/Hardpoints Deployed abbreviation
deployed: 'déployé', // Weapons/Hardpoints Deployed
'detailed export': 'export détaillé',
disabled: 'désactivé',
discount: 'ristourne',
'edit data': 'Editer donnée',
efficiency: 'rendement', // Power Plant efficiency
empty: 'Vide',
'empty all': 'vide tout',
'Enter Name': 'Entrer nom',
Explorer: 'explorateur',
'fastest range': 'gamme la plus rapide', // Fastet totaljump range - sum of succesive jumps
fuel: 'carburant',
'fuel level': 'niveau de carburant', // Percent of fuel (T) in the tank
'full tank': 'Réservoir plein',
hardpoints: 'Points d\'emport',
hull: 'Coque', // Ships hull
import: 'Importer',
insurance: 'Assurance',
'internal compartments': 'compartiments internes',
jump: 'saut', // Single jump range
'jump range': 'Distance de saut',
jumps: 'Sauts',
laden: 'chargé',
language: 'Langage',
maneuverability: 'maniabilité',
manufacturer: 'fabricant',
mass: 'Masse',
'mass lock factor': 'facteur inhibition de masse',
'max mass': 'masse max',
MLF: 'FIM', // Mass Lock Factor Abbreviation
'net cost': 'coûts nets',
no: 'non',
'none created': 'Rien de créé',
ok: 'D\'accord',
'optimal mass': 'masse optimale', // Lowest weight / best weight for jump distance, etc
optimize: 'optimiser',
pen: 'pén.', // Armour peneration abbreviation
permalink: 'lien durable',
power: 'énergie', // Power = Energy / second. Power generated by the power plant, or power consumed (MW / Mega Watts). Used in the power plant section
proceed: 'continuer',
PWR: 'P', // Power Abbreviation. See Power
qty: 'quantité', // Quantity abbreviation
range: 'portée',
rate: 'cadence',
recharge: 'recharger', // Shield Recharge time from 50% -> 100%
recovery: 'récupération', // Shield recovery time (after losing shields/turning on -> 50%)
'refuel time': 'Temps de remplissage', // Time to refuel the tank when scooping
reload: 'recharger', // Reload weapon/hardpoint
'reload costs': 'recharger coûts',
rename: 'renommer',
repair: 'réparer',
reset: 'Réinitialisation',
ret: 'esc', // Retracted abbreviation
retracted: 'escamoté', // Weapons/Hardpoints retracted
'retrofit costs': 'Valeur de rachat', // The cost difference when upgrading / downgrading a component
'retrofit from': 'Racheter de', // Retrofit from Build A against build B
ROF: 'cadence', // Rate of Fire abbreviation
roles: 'rôles', // Commander/Ship build roles - e.g. Trader, Bounty-Hunter, Explorer, etc
save: 'sauvegarder',
sell: 'vendre',
settings: 'paramètres', // Coriolis application settings
shields: 'boucliers',
ship: 'vaisseau',
ships: 'vaisseaux',
shortened: 'raccourci', // Standard/Stock build of a ship when purchased new
size: 'taille',
skip: 'Suivant', // Skip past something / ignore it
speed: 'vitesse',
Stock: 'de base', // Thermal-load abbreviation
strength: 'force', // Strength in reference to Shield Strength
subtotal: 'Sous-Total',
'T-Load': 'degrés', // Thermal load abbreviation
time: 'temps', // time it takes to complete something
tooltips: 'infobulles', // Tooltips setting - show/hide
'total range': 'plage totale',
Trader: 'commerçant', // Trader role
'unit cost': 'coût unitaire',
unladen: 'Non chargé', // No cargo or fuel
'utility mounts': 'Support utilitaire',
WEP: 'ARM', // Abbreviation - Weapon recharge rate for power distributor
yes: 'oui'
};

133
src/app/i18n/fr.json Normal file
View File

@@ -0,0 +1,133 @@
{
"PHRASE_BACKUP_DESC": "Copie des données Coriolis pour l'utilisation dans d'autres sites et outils",
"PHRASE_CONFIRMATION": "Êtes-vous sûr?",
"PHRASE_EXPORT_DESC": "Un export détaillé en JSON de votre configuration pour l'utilisation dans d'autres sites et outils",
"PHRASE_FASTEST_RANGE": "Portée maximale en cas de sauts successifs",
"PHRASE_IMPORT": "Coller JSON ou importer ici",
"PHRASE_LADEN": "Masse du Vaisseau + Carburant + Cargo",
"PHRASE_NO_BUILDS": "Aucune configuration ajoutée pour la comparaison",
"PHRASE_NO_RETROCH": "Aucun changement de configuration",
"PHRASE_SELECT_BUILDS": "Sélectionner les configurations à comparer",
"PHRASE_SG_RECHARGE": "Temps de charge de 50% à 100 %",
"PHRASE_SG_RECOVER": "Temps de redémarrage après perte du bouclier",
"PHRASE_UNLADEN": "Masse du Vaisseau hors Carburant et Cargo",
"PHRASE_UPDATE_RDY": "Mise à jour disponible ! Cliquez pour rafraichir",
"Ls": "SL",
"LY": "AL",
"L": "G",
"large": "grand",
"medium": "moyen",
"S": "P",
"small": "petit",
"about": "à propos",
"added": "ajouté",
"ammo": "munition",
"armour": "Armure",
"available": "Disponibilité",
"backup": "sauvegarde",
"bays": "baies",
"bins": "bacs",
"build": "Configuration",
"build name": "Nom de la configuration",
"builds": "Configurations",
"buy": "Acheter",
"cancel": "Annuler",
"cargo": "Soute",
"cells": "Cellule",
"close": "fermer",
"compare": "comparer",
"compare all": "tout comparer",
"comparison": "comparaison",
"comparisons": "comparaisons",
"cost": "coût",
"costs": "coûts",
"create": "Créer",
"create new": "Créer nouveau",
"credits": "crédits",
"damage": "dégât",
"damage per second": "dégât par seconde",
"delete": "supprimer",
"delete all": "tout supprimer",
"dep": "depl",
"deployed": "déployé",
"detailed export": "export détaillé",
"disabled": "désactivé",
"discount": "ristourne",
"edit data": "Editer donnée",
"efficiency": "rendement",
"empty": "Vide",
"empty all": "vide tout",
"Enter Name": "Entrer nom",
"Explorer": "explorateur",
"farthest range": "gamme la plus rapide",
"fuel": "carburant",
"fuel level": "niveau de carburant",
"full tank": "Réservoir plein",
"hardpoints": "Points d'emport",
"hull": "Coque",
"import": "Importer",
"insurance": "Assurance",
"internal compartments": "compartiments internes",
"jump": "saut",
"jump range": "Distance de saut",
"jumps": "Sauts",
"laden": "chargé",
"language": "Langage",
"maneuverability": "maniabilité",
"manufacturer": "fabricant",
"mass": "Masse",
"mass lock factor": "facteur inhibition de masse",
"max mass": "masse max",
"MLF": "FIM",
"net cost": "coûts nets",
"no": "non",
"none created": "Rien de créé",
"ok": "D'accord",
"optimal mass": "masse optimale",
"optimize": "optimiser",
"pen": "pén.",
"permalink": "lien durable",
"power": "énergie",
"proceed": "continuer",
"PWR": "P",
"qty": "quantité",
"range": "portée",
"rate": "cadence",
"recharge": "recharger",
"recovery": "récupération",
"refuel time": "Temps de remplissage",
"reload": "recharger",
"reload costs": "recharger coûts",
"rename": "renommer",
"repair": "réparer",
"reset": "Réinitialisation",
"ret": "esc",
"retracted": "escamoté",
"retrofit costs": "Valeur de rachat",
"retrofit from": "Racheter de",
"ROF": "cadence",
"roles": "rôles",
"save": "sauvegarder",
"sell": "vendre",
"settings": "paramètres",
"shields": "boucliers",
"ship": "vaisseau",
"ships": "vaisseaux",
"shortened": "raccourci",
"size": "taille",
"skip": "Suivant",
"speed": "vitesse",
"Stock": "de base",
"strength": "force",
"subtotal": "Sous-Total",
"T-Load": "degrés",
"time": "temps",
"tooltips": "infobulles",
"total range": "plage totale",
"Trader": "commerçant",
"unit cost": "coût unitaire",
"unladen": "Non chargé",
"utility mounts": "Support utilitaire",
"WEP": "ARM",
"yes": "oui"
}

View File

@@ -13,117 +13,4 @@ export const formats = {
shortMonths: ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic'] shortMonths: ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic']
}; };
export const terms = { export { default as terms } from './it.json';
PHRASE_EXPORT_DESC: 'Un export dettagliato in formato JSON della tua configurazione per essere usato in altri siti o tools',
'A-Rated': 'Classe A',
about: 'Info su Coriolis',
action: 'azione',
added: 'aggiunto',
Advanced: 'Avanzato',
maneuverability: 'agilità',
ammo: 'munizioni',
PHRASE_CONFIRMATION: 'Sei sicuro ?',
armour: 'armatura',
available: 'disponibile',
bins: 'contenitore',
build: 'configurazione',
'build name': 'Nome Configurazione',
builds: 'configurazioni',
buy: 'compra',
cancel: 'cancella',
cells: 'celle',
close: 'chiudi',
compare: 'confronta',
'compare all': 'confronta tutti',
comparison: 'comparazione',
comparisons: 'comparazioni',
component: 'componente',
cost: 'costo',
costs: 'costi',
cm: 'Contromisure',
create: 'crea',
'create new': 'crea nuovo',
credits: 'crediti',
damage: 'danno',
delete: 'elimina',
'delete all': 'elimina tutto',
dep: 'dep',
deployed: 'deployed',
'detailed export': 'esportazione dettagliata',
disabled: 'disabilita',
discount: 'sconto',
done: 'fatto',
'edit data': 'modifica i dati',
efficiency: 'efficenza',
empty: 'vuoto',
Enforcer: 'Rinforzatore',
'enter name': 'Inserisci un nome',
export: 'esporta',
fixed: 'fissi',
fuel: 'carburante',
'full tank': 'Serbatoio Pieno',
huge: 'enorme',
hull: 'corazza',
import: 'importa',
'import all': 'importa tutto',
insurance: 'assicurazione',
'internal compartments': 'compartimenti interni',
'jump range': 'distanza di salto',
jumps: 'salti',
laden: 'carico',
language: 'lingua',
large: 'largo',
mass: 'massa',
max: 'massimo',
'max mass': 'massa massimale',
medium: 'medio',
'net cost': 'costo netto',
PHRASE_NO_BUILDS: 'nessuna configurazione è stata aggiunta per la comparazione!',
PHRASE_NO_RETROCH: 'Nessun cambiamento di Retrofitting',
none: 'nessuno',
'none created': 'nessuno creato',
optimal: 'ottimale',
'optimal mass': 'massa ottimale',
'optimize mass': 'ottimizza la massa',
overwrite: 'sovrasscrivi',
PHRASE_IMPORT: 'Incolla un JSON o importalo qua',
penetration: 'penetrazione',
power: 'potenza',
priority: 'priorità',
proceed: 'procedi',
range: 'distanza',
rate: 'rateo',
recharge: 'ricarica',
reload: 'ricarica',
rename: 'rinomina',
repair: 'ripara',
reset: 'resetta',
retracted: 'retratti',
'retrofit costs': 'costi di retrofit',
'retrofit from': 'retrofit da',
save: 'salva',
sell: 'vendi',
settings: 'impostazioni',
shields: 'scudi',
ship: 'nave',
ships: 'navi',
shortened: 'accorciato',
size: 'grandezza',
skip: 'salta',
small: 'piccolo',
speed: 'velocità',
Stock: 'appena comprata',
t: 'thrusters',
time: 'tempo',
total: 'totale',
'total range': 'distanza totale',
turret: 'torrette',
type: 'tipo',
unladen: 'scarico',
PHRASE_UPDATE_RDY: 'Aggiornamenti disponibili ! Clicca per Aggiornare',
utility: 'supporti',
'utility mounts': 'supporti di utilità',
version: 'versione',
yes: 'sì',
PHRASE_BACKUP_DESC: 'Esportazione di tutti i dati su Coriolis per salvarli o trasferirli in un altro Browser/dispositivo'
};

114
src/app/i18n/it.json Normal file
View File

@@ -0,0 +1,114 @@
{
"PHRASE_EXPORT_DESC": "Un export dettagliato in formato JSON della tua configurazione per essere usato in altri siti o tools",
"A-Rated": "Classe A",
"about": "Info su Coriolis",
"action": "azione",
"added": "aggiunto",
"Advanced": "Avanzato",
"maneuverability": "agilità",
"ammo": "munizioni",
"PHRASE_CONFIRMATION": "Sei sicuro ?",
"armour": "armatura",
"available": "disponibile",
"bins": "contenitore",
"build": "configurazione",
"build name": "Nome Configurazione",
"builds": "configurazioni",
"buy": "compra",
"cancel": "cancella",
"cells": "celle",
"close": "chiudi",
"compare": "confronta",
"compare all": "confronta tutti",
"comparison": "comparazione",
"comparisons": "comparazioni",
"component": "componente",
"cost": "costo",
"costs": "costi",
"cm": "Contromisure",
"create": "crea",
"create new": "crea nuovo",
"credits": "crediti",
"damage": "danno",
"delete": "elimina",
"delete all": "elimina tutto",
"dep": "dep",
"deployed": "deployed",
"detailed export": "esportazione dettagliata",
"disabled": "disabilita",
"discount": "sconto",
"done": "fatto",
"edit data": "modifica i dati",
"efficiency": "efficenza",
"empty": "vuoto",
"Enforcer": "Rinforzatore",
"enter name": "Inserisci un nome",
"export": "esporta",
"fixed": "fissi",
"fuel": "carburante",
"full tank": "Serbatoio Pieno",
"huge": "enorme",
"hull": "corazza",
"import": "importa",
"import all": "importa tutto",
"insurance": "assicurazione",
"internal compartments": "compartimenti interni",
"jump range": "distanza di salto",
"jumps": "salti",
"laden": "carico",
"language": "lingua",
"large": "largo",
"mass": "massa",
"max": "massimo",
"max mass": "massa massimale",
"medium": "medio",
"net cost": "costo netto",
"PHRASE_NO_BUILDS": "nessuna configurazione è stata aggiunta per la comparazione!",
"PHRASE_NO_RETROCH": "Nessun cambiamento di Retrofitting",
"none": "nessuno",
"none created": "nessuno creato",
"optimal": "ottimale",
"optimal mass": "massa ottimale",
"optimize mass": "ottimizza la massa",
"overwrite": "sovrasscrivi",
"PHRASE_IMPORT": "Incolla un JSON o importalo qua",
"penetration": "penetrazione",
"power": "potenza",
"priority": "priorità",
"proceed": "procedi",
"range": "distanza",
"rate": "rateo",
"recharge": "ricarica",
"reload": "ricarica",
"rename": "rinomina",
"repair": "ripara",
"reset": "resetta",
"retracted": "retratti",
"retrofit costs": "costi di retrofit",
"retrofit from": "retrofit da",
"save": "salva",
"sell": "vendi",
"settings": "impostazioni",
"shields": "scudi",
"ship": "nave",
"ships": "navi",
"shortened": "accorciato",
"size": "grandezza",
"skip": "salta",
"small": "piccolo",
"speed": "velocità",
"Stock": "appena comprata",
"t": "thrusters",
"time": "tempo",
"total": "totale",
"total range": "distanza totale",
"turret": "torrette",
"type": "tipo",
"unladen": "scarico",
"PHRASE_UPDATE_RDY": "Aggiornamenti disponibili ! Clicca per Aggiornare",
"utility": "supporti",
"utility mounts": "supporti di utilità",
"version": "versione",
"yes": "sì",
"PHRASE_BACKUP_DESC": "Esportazione di tutti i dati su Coriolis per salvarli o trasferirli in un altro Browser/dispositivo"
}

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

@@ -0,0 +1,16 @@
export const formats = {
decimal: '.',
thousands: ',',
grouping: [3],
currency: ['$', ''],
dateTime: '%a %b %e %X %Y',
date: '%m/%d/%Y',
time: '%H:%M:%S',
periods: ['AM', 'PM'],
days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
shortDays: ['Nie', 'Pon', 'Wt', 'Śr', 'Czw', 'Pt', 'Sob'],
months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
shortMonths: ['Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze', 'Lip', 'Sie', 'Wrz', 'Paź', 'Lis', 'Gru']
};
export { default as terms } from './pl.json';

59
src/app/i18n/pl.json Normal file
View File

@@ -0,0 +1,59 @@
{
"PHRASE_ALT_ALL": "Alt + kliknięcie by wypełnić wszystkie sloty",
"PHRASE_BACKUP_DESC": "Kopia zapasowa wszystkich danych Coriolis w celu zapisu lub przeniesienia na inne urządzenie/przeglądarkę",
"PHRASE_CONFIRMATION": "Czy jesteś pewien?",
"PHRASE_EXPORT_DESC": "Szczegółowy eksport schematu w formacie JSON w celu użycia na innych stronach i narzędziach",
"PHRASE_FASTEST_RANGE": "Maksymalna ilość skoków na najwyższym zasięgu",
"PHRASE_IMPORT": "Wklej tu JSON lub importuj",
"PHRASE_LADEN": "Masa statku + paliwo + ładunek",
"PHRASE_NO_BUILDS": "Nie dodano schematu do porównania!",
"PHRASE_NO_RETROCH": "Brak zmian retrofit",
"PHRASE_SELECT_BUILDS": "Wybierz schematy do porównania",
"PHRASE_SG_RECHARGE": "Czas od 50% do 100% naładowania",
"PHRASE_SG_RECOVER": "Odnowienie (do 50%) po upadku",
"PHRASE_UNLADEN": "Masa statku z wyłączeniem paliwa i ładunku",
"PHRASE_UPDATE_RDY": "Dostępna aktualizacja! Naciśnij by odświeżyć",
"am": "Auto Field-Maintenance Unit",
"bh": "Bulkheads",
"bl": "Beam Laser",
"bsg": "Bi-Weave Shield Generator",
"c": "Cannon",
"cc": "Collector Limpet Controller",
"cm": "Countermeasure",
"cr": "Cargo Rack",
"cs": "Cargo Scanner",
"dc": "Docking Computer",
"fc": "Fragment Cannon",
"fi": "FSD Interdictor",
"fs": "Fuel Scoop",
"fsd": "Frame Shift Drive",
"ft": "Fuel Tank",
"fx": "Fuel Transfer Limpet Controller",
"hb": "Hatch Breaker Limpet Controller",
"hr": "Hull Reinforcement Package",
"kw": "Kill Warrant Scanner",
"ls": "Life Support",
"mc": "Multi-cannon",
"ml": "Mining Laser",
"mr": "Missile Rack",
"nl": "Mine Launcher",
"pa": "Plasma Accelerator",
"pas": "Planetary Approach Suite",
"pc": "Prospector Limpet Controller",
"pd": "power distributor",
"pl": "Pulse Laser",
"pp": "Power Plant",
"psg": "Prismatic Shield Generator",
"pv": "Planetary Vehicle Hangar",
"rf": "Refinery",
"rg": "Rail Gun",
"s": "Sensors",
"sb": "Shield Booster",
"sc": "Scanner",
"scb": "Shield Cell Bank",
"sg": "Shield Generator",
"t": "thrusters",
"tp": "Torpedo Pylon",
"ul": "Burst Laser",
"ws": "Frame Shift Wake Scanner"
}

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

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

584
src/app/i18n/pt.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -13,152 +13,4 @@ export const formats = {
shortMonths: ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'] shortMonths: ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек']
}; };
export const terms = { export { default as terms } from './ru.json';
// Phrases
PHRASE_BACKUP_DESC: 'Сохраните все данные перед переносом в другой браузер или устройство', // Backup of all Coriolis data to save or transfer to another browser/device
PHRASE_CONFIRMATION: 'Вы уверены?', // Are You Sure?
PHRASE_EXPORT_DESC: 'Детальный JSON-экспорт вашей сборки для использования в других местах и инструментах', // A detailed JSON export of your build for use in other sites and tools
PHRASE_FASTEST_RANGE: 'Последовательные максимальные дальности прыжков', // Consecutive max range jumps
PHRASE_IMPORT: 'Для импорта вставьте код в эту форму', // Paste JSON or import here
PHRASE_LADEN: 'Корабль Масса + топлива + Грузы', // Ship Mass + Fuel + Cargo
PHRASE_NO_BUILDS: 'Нечего сравнивать', // No builds added to comparison!
PHRASE_NO_RETROCH: 'нет ранних версий сборкиконфигурации', // No Retrofitting changes
PHRASE_SELECT_BUILDS: 'Выберите конфигурацию для сравнения', // Select Builds to Compare
PHRASE_SG_RECHARGE: 'восстановление с 60% до 100% объема щита', // Time from 50% to 100% Charge
PHRASE_SG_RECOVER: 'восстановление [до 60%] после падения\сбития', // Recovery (to 50%) after collapse
PHRASE_UNLADEN: 'Судно Масса без учета топлива и грузов', // Ship Mass excluding Fuel and Cargo
PHRASE_UPDATE_RDY: 'Доступно обновление. Нажмите для обновления.', // Update Available! Click to Refresh
// Units / Metrics
'/s': '/с', // Per second
'm/s': 'м/с', // Meters / Second
Ls: 'Св.сек', // Light seconds
LY: 'Св.лет', // Light Years
CR: 'кр.', // Credits abbreviation
// Sizes
S: 'M', // Small Hardpoint (single Character)
M: 'С', // Medium Hardpoint size (single character)
L: 'б', // Large Hardpoint size (single character)
H: 'O', // Huge Hardpoint size (single character)
U: 'B', // Utility Hardpoint size (single character) - Kill warrant scanner, etc
small: 'Малый', // Small ship size
medium: 'Средний', // Medium ship size
large: 'большой', // Large Ship Size
// Insurance
alpha: 'Альфа', // Alpha backer insurance level
beta: 'Бета', // Beta back insurance level
standard: 'Стандартный', // Standard insurance level
// Terms
'build name': 'название сборки', // Ship build/configuration/design name
'compare all': 'сравнить все',
'create new': 'Создать новый',
'damage per second': 'урон в секунду',
'delete all': 'Удалить все',
'detailed export': 'Подробный экспорт',
'edit data': 'Редактирование',
'empty all': 'пусто все',
'Enter Name': 'Введите имя',
'fastest range': 'быстрый диапазон', // Fastet totaljump range - sum of succesive jumps
'fuel level': 'уровень топлива', // Percent of fuel (T) in the tank
'full tank': 'Полный бак',
'internal compartments': 'внутренние отсеки',
'jump range': 'Дальность прыжка',
'mass lock factor': 'Масс. блок',
'max mass': 'Максимальная масса',
'net cost': 'разница в цене',
'none created': 'не создано',
'refuel time': 'Время дозаправки', // Time to refuel the tank when scooping
'retrofit costs': 'цена модификации', // The cost difference when upgrading / downgrading a component
'retrofit from': 'модификация от', // Retrofit from Build A against build B
'T-Load': 'Тепл.', // Thermal load abbreviation
'utility mounts': 'Вспомогательное оборудование',
about: 'О ...', // Link to about page / about Coriolis.io
action: 'Действие',
added: 'Добавлено',
ammo: 'Боекомплект', // Ammunition
armour: 'Броня',
available: 'доступно', // Available options
backup: 'Резервная копия',
bins: 'контейнеры', // Number of Mining Refinery bins
boost: 'форсаж',
build: 'cборка', // Shorthand for the build/configuration/design name
builds: 'cборки', // Ship build/configuration/design names
buy: 'купить',
cancel: 'отменить',
cargo: 'Груз',
cells: 'Ячейки', // Number of cells in a shield cell bank
close: 'закрыть',
compare: 'сравнить ',
comparison: 'сравнение',
comparisons: 'сравнения',
cost: 'Стоимость', // Cost / price of a module or price of a ship
costs: 'Расходы', // Costs / prices of a modules or prices of ships
create: 'создать',
credits: 'Кредиты',
damage: 'Урон',
delete: 'Удалить',
dep: 'Вып', // Weapons/Hardpoints Deployed abbreviation
deployed: 'Открыты', // Weapons/Hardpoints Deployed
disabled: 'Отключено',
discount: 'Скидка',
DPS: 'УВС', // Damage per second abbreviation
efficiency: 'Эффективность', // Power Plant efficiency
empty: 'пусто',
ENG: 'ДВГ', // Abbreviation - Engine recharge rate for power distributor
export: 'Экспорт',
forum: 'Форум',
fuel: 'Топливо',
hardpoints: 'Орудийные порты',
hull: 'Корпус', // Ships hull
import: 'импортировать ',
insurance: 'Страховка',
jumps: 'Прыжков',
laden: 'Груженый',
language: 'Язык',
maneuverability: 'Моневренность',
mass: 'Масса',
max: 'Макс',
no: 'Нет',
pen: 'ПБ', // Armour peneration abbreviation
permalink: 'Постоянная ссылка',
power: 'Мощность', // Power = Energy / second. Power generated by the power plant, or power consumed (MW / Mega Watts). Used in the power plant section
pri: 'Осн', // Priority abbreviation for power management
proceed: 'продолжить',
PWR: 'Эн', // Power Abbreviation. See Power
range: 'Дальность',
rate: 'скорость',
recharge: 'перезарядка', // Shield Recharge time from 50% -> 100%
recovery: 'включение', // Shield recovery time (after losing shields/turning on -> 50%)
reload: 'Перезагрузить', // Reload weapon/hardpoint
rename: 'Переименовать',
repair: 'Починка',
reset: 'Сброс',
ret: 'Убр.', // Retracted abbreviation
retracted: 'Убрано', // Weapons/Hardpoints retracted
ROF: 'В/сек', // Rate of Fire abbreviation
save: 'Сохранить',
sell: 'Продать',
settings: 'Настройки', // Coriolis application settings
shields: 'Щиты',
ship: 'Корабль',
ships: 'Корабли',
shortened: 'Укороченный', // Standard/Stock build of a ship when purchased new
size: 'размер',
skip: 'пропустить', // Skip past something / ignore it
speed: 'скорость',
standard: 'Стандартный', // Standard / Common modules (FSD, power plant, life support, etc)
Stock: 'Стандартная комплектация', // Thermal-load abbreviation
SYS: 'СИСТЕМЫ', // Abbreviation - System recharge rate for power distributor
time: 'Время', // time it takes to complete something
total: 'Всего',
type: 'Тип',
unladen: 'Пустой', // No cargo or fuel
URL: 'Ссылка', // Link, Uniform Resource Locator
WEP: 'ОРУДИЯ', // Abbreviation - Weapon recharge rate for power distributor
yes: 'Да'
};

773
src/app/i18n/ru.json Normal file
View File

@@ -0,0 +1,773 @@
{
"PHRASE_ALT_ALL": "Alt + Нажатие для заполнения всех слотов",
"PHRASE_BACKUP_DESC": "Сохраните все данные перед переносом в другой браузер или устройство",
"PHRASE_CONFIRMATION": "Вы уверены?",
"PHRASE_EXPORT_DESC": "Детальный JSON-экспорт вашей сборки для использования в других местах и инструментах",
"PHRASE_FASTEST_RANGE": "Последовательные прыжки максимальной дальности",
"PHRASE_IMPORT": "Для импорта вставьте код в эту форму",
"PHRASE_LADEN": "Масса корабля с учетом топлива и грузов",
"PHRASE_NO_BUILDS": "Нечего сравнивать",
"PHRASE_NO_RETROCH": "Нет ранних версий сборки",
"PHRASE_SELECT_BUILDS": "Выберите конфигурацию для сравнения",
"PHRASE_SG_RECHARGE": "Восстановление с 50% до 100% объема щита, учитывая полный аккумулятор СИС в начале",
"PHRASE_SG_RECOVER": "Восстановление с 0% до 50% объема щита, учитывая полный аккумулятор СИС в начале",
"PHRASE_UNLADEN": "Масса корабля без учета топлива и грузов",
"PHRASE_UPDATE_RDY": "Доступна новая версия. Нажмите для обновления.",
"PHRASE_ENGAGEMENT_RANGE": "Дистанция между кораблем и целью",
"PHRASE_SELECT_BLUEPRINT": "Нажмите чтобы выбрать чертеж",
"PHRASE_BLUEPRINT_WORST": "Худшие основные значения для чертежа",
"PHRASE_BLUEPRINT_FIFTY": "50% значения для чертежа",
"PHRASE_BLUEPRINT_SEVEN_FIVE": "75% значения для чертежа",
"PHRASE_BLUEPRINT_RANDOM": "Случайный выбор между худшими и лучшими значениями для этого чертежа",
"PHRASE_BLUEPRINT_BEST": "Лучшие основные значения для чертежа",
"PHRASE_BLUEPRINT_EXTREME": "Лучшие положительные и худшие отрицательные основные значения для чертежа",
"PHRASE_BLUEPRINT_RESET": "Сбросить все модификаторы и чертеж",
"PHRASE_SELECT_SPECIAL": "Нажмите, чтобы выбрать экспериментальный эффект",
"PHRASE_NO_SPECIAL": "Без экспериментального эффекта",
"PHRASE_SHOPPING_LIST": "Станции, что продают эту сборку",
"PHRASE_SHOPPING_MATS": "Материалы которые нужны для сборки",
"PHRASE_REFIT_SHOPPING_LIST": "Станции, что продают необходимые модули",
"PHRASE_TOTAL_EFFECTIVE_SHIELD": "Общий урон, что может быть нанесен в каждым типе, если используются все щитонакопители",
"PHRASE_TIME_TO_LOSE_SHIELDS": "Щиты продержатся",
"PHRASE_TIME_TO_RECOVER_SHIELDS": "Щиты восстановятся за",
"PHRASE_TIME_TO_RECHARGE_SHIELDS": "Щиты будут заряжены за",
"PHRASE_SHIELD_SOURCES": "Подробности энергии щита",
"PHRASE_EFFECTIVE_SHIELD": "Эффективная сила щита против разных типов урона",
"PHRASE_ARMOUR_SOURCES": "Подробности состава брони",
"PHRASE_EFFECTIVE_ARMOUR": "Эффективная сила брони против разных типов урона",
"PHRASE_DAMAGE_TAKEN": "% общих повреждений полученных в разных типах урона",
"PHRASE_TIME_TO_LOSE_ARMOUR": "Броня продержится",
"PHRASE_MODULE_PROTECTION_EXTERNAL": "Защита гнезд",
"PHRASE_MODULE_PROTECTION_INTERNAL": "Защита всех остальных модулей",
"PHRASE_OVERALL_DAMAGE": "Разбивка источников устойчивого ДПС",
"PHRASE_SHIELD_DAMAGE": "Подробности источников поддерживаемого ДПС против щитов",
"PHRASE_ARMOUR_DAMAGE": "Подробности источников поддерживаемого ДПС против брони",
"PHRASE_TIME_TO_REMOVE_SHIELDS": "Снимет щиты за",
"PHRASE_MULTI_CREW_CAPACITOR_POINTS": "Щелкните правой кновкой мыши чтобы объединить в группу.",
"TT_TIME_TO_REMOVE_SHIELDS": "Непрерывным огнем из всех орудий",
"PHRASE_TIME_TO_REMOVE_ARMOUR": "Снимет броню за",
"TT_TIME_TO_REMOVE_ARMOUR": "Непрерывным огнем из всех орудий",
"PHRASE_TIME_TO_DRAIN_WEP": "Опустошит ОРУ за",
"TT_TIME_TO_DRAIN_WEP": "Время, за которое опустошится аккумулятор ОРУ при стрельбе из всех орудий",
"TT_TIME_TO_LOSE_SHIELDS": "Против поддерживаемой стрельбы из всех орудий противника",
"TT_TIME_TO_LOSE_ARMOUR": "Против поддерживаемой стрельбы из всех орудий противника",
"TT_MODULE_ARMOUR": "Броня, защищающая модули от урона",
"TT_MODULE_PROTECTION_EXTERNAL": "Процент урона, перенаправленного от гнезд на наборы для усиления модулей",
"TT_MODULE_PROTECTION_INTERNAL": "Процент урона, перенаправленного от модулей вне гнезд на наборы для усиления модулей",
"TT_EFFECTIVE_SDPS_SHIELDS": "Реальный поддерживаемый ДПС пока аккумулятор ОРУ не пуст",
"TT_EFFECTIVENESS_SHIELDS": "Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью без пунктов в СИС на 0 метрах",
"TT_EFFECTIVE_SDPS_ARMOUR": "Реальный поддерживаемый ДПС пока аккумулятор ОРУ не пуст",
"TT_EFFECTIVENESS_ARMOUR": "Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью на 0 метрах",
"PHRASE_EFFECTIVE_SDPS_SHIELDS": "ПДПС против щитов",
"PHRASE_EFFECTIVE_SDPS_ARMOUR": "ПДПС против брони",
"TT_SUMMARY_SPEED": "С полным топливным баком и 4 пунктами в ДВГ",
"TT_SUMMARY_SPEED_NONFUNCTIONAL": "Маневровые двигатели выключены или превышена максимальная масса с топливом и грузом",
"TT_SUMMARY_BOOST": "С полным топливным баком и 4 пунктами в ДВГ",
"TT_SUMMARY_BOOST_INTERVAL": "Время заполнения с 4 пунктами в СИС",
"TT_SUMMARY_BOOST_NONFUNCTIONAL": "Распределитель питания не может обеспечить достаточно энергии для форсажа",
"TT_SUMMARY_SHIELDS": "Чистая сила щита, включая усилители",
"TT_SUMMARY_SHIELDS_SCB": "Прочность щита, включая бустеры и SCB",
"TT_SUMMARY_SHIELDS_NONFUNCTIONAL": "Шитогенератор отсутствует или выключен",
"TT_SUMMARY_INTEGRITY": "Целостность корабля, включая переборки и наборы для усиления корпуса",
"TT_SUMMARY_HULL_MASS": "Масса корпуса без каких-либо модулей",
"TT_SUMMARY_UNLADEN_MASS": "Масса корпуса и модулей без топлива и груза",
"TT_SUMMARY_LADEN_MASS": "Масса корпуса и модулей с топливом и грузом",
"TT_SUMMARY_DPS": "Урон в секунду при стрельбе из всех орудий",
"TT_SUMMARY_EPS": "Расход аккумулятора ОРУ в секунду при стрельбе из всех орудий",
"TT_SUMMARY_TTD": "Время расхода аккумулятора ОРУ при стрельбе из всех орудий и с 4 пунктами в ОРУ",
"TT_SUMMARY_MAX_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с топливом, достаточным только на сам прыжок",
"TT_SUMMARY_UNLADEN_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с полным топливным баком",
"TT_SUMMARY_LADEN_SINGLE_JUMP": "Самый дальний возможный прыжок с полным грузовым отсеком и с полным топливным баком",
"TT_SUMMARY_UNLADEN_TOTAL_JUMP": "Самая дальняя общая дистанция без груза, с полным топливным баком и при прыжках на максимальное расстояние",
"TT_SUMMARY_LADEN_TOTAL_JUMP": "Самая дальняя общая дистанция с полным грузовым отсеком, с полным топливным баком и при прыжках на максимальное расстояние",
"HELP_MODIFICATIONS_MENU": "Нажмите на номер, чтобы ввести новое значение, или потяните вдоль полосы для малых изменений",
"PHRASE_FAIL_EDENGINEER": "Не удалось отправить в EDEngineer (запустите EDEngineer и убедитесь, что API запущен, затем обновите страницу).",
"PHRASE_FIREFOX_EDENGINEER": "Отправка в EDEngineer несовместима с настройками безопасности Firefox. Пожалуйста, попробуйте еще раз в Google Chrome.",
"am": "Блок Автом. Полевого Ремонта",
"bh": "Переборки",
"bl": "Пучковый лазер",
"bsg": "Двухпоточный щитогенератор",
"c": "Пушка",
"causres": "Каустическое сопротивление",
"Caustic resistance": "Каустическое сопротивление",
"cc": "Контроллер магнитного снаряда для сбора",
"ch": "Разбрасыватель дипольных отражателей",
"cr": "Грузовой стеллаж",
"cs": "Сканер содержимого",
"dc": "Стыковочный компьютер",
"ec": "Электр. противодействие",
"fc": "Залповое орудие",
"fh": "Ангар для истребителя",
"fi": "FSD-перехватчик",
"fs": "Топливозаборник",
"fsd": "Рамочно-сместительный двигатель",
"ft": "Топливный бак",
"fx": "Контроллер магнитного снаряда для топлива",
"hb": "Контроллер магнитного снаряда для взлома трюма",
"hr": "Набор для усиления корпуса",
"hs": "Теплоотводная катапульта",
"kw": "Сканер преступников",
"ls": "Система жизнеобеспечения",
"mc": "Многоствольное орудие",
"axmc": "Многоствольное орудие АИ",
"ml": "Проходочный лазер",
"mr": "Ракетный лоток",
"axmr": "Блок ракет АИ",
"mrp": "Набор для усиления модуля",
"nl": "Мины",
"pa": "Ускоритель плазмы",
"pas": "Комплект для сближения с планетой",
"pc": "Контроллер магнитного снаряда для геологоразведки",
"pce": "Каюта пассажира эконом-класса",
"passenger capacity": "Количество пассажиров",
"pci": "Каюта пассажира бизнес-класса",
"pcm": "Каюта пассажира первого класса",
"pcq": "Каюта пассажира класса люкс",
"pd": "Распределитель питания",
"pl": "Импульсный лазер",
"po": "Точечная оборона",
"pp": "Силовая установка",
"gpp": "Силовая установка Стражей",
"gpd": "Гибридный распределитель питания Стражей",
"gpc": "Плазменная пушка Стражей",
"ggc": "Пушка Гаусса Стражей",
"gsrp": "Набор для усиления щита Стражей",
"gfsb": "Ускоритель FSD Стражей",
"ghrp": "Набор для усиления корпуса Стражей",
"gmrp": "Набор для усиления модуля Стражей",
"pwa": "Анализатор импульсных волн",
"abl": "Абразивный бластер",
"scl": "Пусковая установка для сейсмических снарядов",
"sdm": "Вытесняющая ракета для добычи глубинных залежей",
"tbsc": "Шоковое орудие",
"gsc": "Осколочное орудие Стражей",
"psg": "Призматический щитогенератор",
"pv": "Гараж для планетарного транспорта",
"rf": "Устройство переработки",
"rfl": "Зенитная установка (снаряды с дистанционным подрывом)",
"rg": "Электромагнитная пушка",
"rsl": "Дроны-исследователи",
"s": "Сенсоры",
"sb": "Усилитель щита",
"sc": "Сканер обнаружения",
"scb": "Щитонакопитель",
"sfn": "Нейтрализатор глушащего поля",
"sg": "Щитогенератор",
"ss": "Сканер поверхностей",
"sua": "Помощь в гиперкрейсерском режиме",
"t": "Маневровые двигатели",
"tp": "Торпедная стойка",
"ul": "Пульсирующие лазеры",
"Send To EDEngineer": "Отправить в EDEngineer",
"ws": "Сканер следа FSD",
"rpl": "Дроны-ремонтники",
"rcpl": "Дроны-разведчики",
"xs": "Сканер «инопланетянин»",
"tbem": "Блок энзимных ракет",
"tbrfl": "Установка для стрельбы стреловидными снарядами с дистанционным подрывом",
"dtl": "Дроны-очистители",
"mahr": "Набор для усиления корпуса из Метасплава",
"emptyrestricted": "пусто (ограниченно)",
"damage dealt to": "Урон нанесен",
"damage received from": "Урон получен от",
"against shields": "Против щитов",
"against hull": "Против корпуса",
"total effective shield": "Общие эффективные щиты",
"ammunition": "Припасы",
"secs": "с",
"bays": "Ячейки",
"rebuildsperbay": "Истребителей в ячейке",
"mroll": "Roll",
"feature": "Свойство",
"worst": "Худшее",
"average": "Среднее",
"random": "Случайное",
"best": "Лучшее",
"current": "Текущее",
"extreme": "Экстремальное",
"reset": "Сброс",
"dpe": "Урон на МДж энергии",
"dps": "Урон в Секунду",
"sdps": "Поддерживаемый урон в секунду",
"dpssdps": "Урон в секунду (поддерживаемый урон в секунду)",
"eps": "Энергия в секунду",
"epsseps": "Энергия в секунду (поддерживаемая энергия в секунду)",
"fallofffromrange": "Спад",
"falloff": "Спад",
"hps": "Нагрев в секунду",
"hpsshps": "Нагрев в секунду (поддерживаемый нагрев в секунду)",
"damage by": "Урон",
"damage from": "Урон от",
"shield cells": "Щитонакопители",
"recovery": "включение",
"recharge": "перезарядка",
"engine pips": "Пункты в двигателе",
"4b": "4 пункта и Форсаж",
"speed": "скорость",
"pitch": "Тангаж",
"roll": "Крен",
"yaw": "Рыскание",
"internal protection": "Внутренняя защита",
"external protection": "Внешняя защита",
"engagement range": "Боевое расстояние",
"boost interval": "Интервал повыш.",
"total": "Всего",
"ammo": "Макс. боекомплект",
"boot": "Время загрузки",
"hacktime": "Время взлома",
"brokenregen": "Скорость восстановления при пробое",
"burst": "Длина очереди",
"burstrof": "Скорострельность очереди",
"clip": "Размер боекомплекта",
"damage": "Урон",
"distdraw": "Тяга распределителя",
"duration": "Продолжительность",
"eff": "Эффективность",
"engcap": "Ресурс двигателей",
"engrate": "Перезарядка двигателей",
"explres": "Сопротивление взрывам",
"facinglimit": "Ограничение по направлению",
"hullboost": "Увеличение корпуса",
"hullreinforcement": "Укрепление корпуса",
"integrity": "Целостность",
"jitter": "Дрожание",
"kinres": "Сопротивление кинетическому урону",
"maxfuel": "Макс. топлива на прыжок",
"mass": "Масса",
"optmass": "Оптимизированная масса",
"optmul": "Оптимальный усилитель",
"pgen": "Мощность",
"piercing": "Бронебойность",
"power": "Мощность",
"protection": "Защита от повреждений",
"range": "Дальность",
"ranget": "Дальность",
"regen": "Скорость восстановления",
"reload": "Перезагрузить",
"rof": "Скорострельность",
"angle": "Угол сканера",
"scanrate": "Скорость сканера",
"proberadius": "Радиус зонда",
"scantime": "Время сканирования",
"scan range": "Дальность",
"max angle": "Макс. угол",
"shield": "Щит",
"armour": "Броня",
"shieldboost": "Усиление щитов",
"shieldreinforcement": "Усилитель щита",
"shotspeed": "Скорость выстрела",
"shotdmg": "Урон за выстрел(DPS)",
"spinup": "Время раскрутки",
"syscap": "Ресурс систем",
"sysrate": "Перезарядка систем",
"thermload": "Тепловая нагрузка",
"thermres": "Сопротивление термическому урону",
"wepcap": "Орудийный ресурс",
"weprate": "Перезарядка оружия",
"minmass_sg": "Мин. масса корпуса",
"optmass_sg": "Опт. масса корпуса",
"maxmass_sg": "Макс. масса корпуса",
"minmul_sg": "Минимальная прочность",
"optmul_sg": "Оптимальная прочность",
"maxmul_sg": "Максимальная прочность",
"minmass_psg": "Мин. масса корпуса",
"optmass_psg": "Опт. масса корпуса",
"maxmass_psg": "Макс. масса корпуса",
"minmul_psg": "Минимальная прочность",
"optmul_psg": "Оптимальная прочность",
"maxmul_psg": "Максимальная прочность",
"minmass_bsg": "Мин. масса корпуса",
"optmass_bsg": "Опт. масса корпуса",
"maxmass_bsg": "Макс. масса корпуса",
"minmul_bsg": "Минимальная прочность",
"optmul_bsg": "Оптимальная прочность",
"maxmul_bsg": "Максимальная прочность",
"range_s": "Типовой диапозон выброса",
"absolute": "Общий",
"explosive": "Взрывч.",
"kinetic": "Механич.",
"thermal": "Тепл.",
"caustic": "Каустич.",
"generator": "Генератор",
"boosters": "Усилители",
"cells": "Ячейки",
"shield addition": "ДОбавления к щиту",
"jump addition": "ДОбавления к прыжку",
"bulkheads": "Переборки",
"reinforcement": "Усилители",
"power and costs": "Энергия и стоимость",
"costs": "Расходы",
"retrofit costs": "цена модификации",
"reload costs": "Стоимость перезарядки",
"profiles": "Графики",
"engine profile": "Двигатели",
"fsd profile": "FSD",
"movement profile": "Движение",
"damage to opponent's shields": "Урон щиту противника",
"damage to opponent's hull": "Урон корпусу противника",
"offence": "Нападение",
"defence": "Защита",
"shield metrics": "Данные щита",
"raw shield strength": "Чистая мощность щита",
"shield sources": "Ресурсы щита",
"damage taken": "Полученный урон",
"effective shield": "Эффективный щит",
"armour metrics": "Данные брони",
"raw armour strength": "Чистая мощность брони",
"armour sources": "Ресурсы брони",
"raw module armour": "Чистая броня модулей",
"effective armour": "Эффективная броня",
"offence metrics": "Данные нападения",
"defence metrics": "Данные обороны",
"fuel carried": "Топливо на борту",
"cargo carried": "Груз на борту",
"ship control": "Управление кораблем",
"opponent": "Противник",
"opponent's shields": "Щит противника",
"opponent's armour": "Броня противника",
"overall damage": "общий урон",
"overall": "общий",
"shield damage sources": "источники урона по щиту",
"armour damage sources": "источники урона по броне",
"never": "Никогда",
"stock": "базовый",
"boost": "форсаж",
"tab_defence": "защита",
"federation rank 1": "Рекрут",
"federation rank 2": "Кадет",
"federation rank 3": "Гардемарин",
"federation rank 4": "Старшина",
"federation rank 5": "Главный старшина",
"federation rank 6": "Уорент-офицер",
"federation rank 7": "Энсин",
"federation rank 8": "Лейтенант",
"federation rank 9": "Капитан-лейтенант",
"federation rank 10": "Начальник гарнизона",
"federation rank 11": "Командир корабля",
"federation rank 12": "Контр-адмирал",
"federation rank 13": "Вице-адмирал",
"federation rank 14": "Адмирал",
"federation rank required": "Минимальный ранг федерации для покупки",
"empire rank 1": "Чужак",
"empire rank 2": "Крепостной",
"empire rank 3": "Мастер",
"empire rank 4": "Оруженосец",
"empire rank 5": "Рыцарь",
"empire rank 6": "Лорд",
"empire rank 7": "Барон",
"empire rank 8": "Виконт",
"empire rank 9": "Граф",
"empire rank 10": "Эрл",
"empire rank 11": "Маркиз",
"empire rank 12": "Герцог",
"empire rank 13": "Принц",
"empire rank 14": "Король",
"empire rank required": "Минимальный ранг империи для покупки",
"kg": "кг",
"kg/s": "кг/с",
"km": "км",
"m": "м",
"MJ": "МДж",
"MW": "МВт",
"T": "т",
"°/s": "°/с",
"/s": "/с",
"/min": "/мин",
"m/s": "м/с",
"Ls": "Св.сек",
"LY": "Св.лет",
"CR": "кр.",
"S": "М",
"M": "С",
"L": "Б",
"H": "О",
"U": "В",
"small": "Малый",
"medium": "Средний",
"large": "Большой",
"alpha": "Альфа",
"beta": "Бета",
"standard": "Стандартный",
"build name": "название сборки",
"compare all": "сравнить все",
"create new": "Создать новый",
"damage per second": "урон в секунду",
"delete all": "Удалить все",
"detailed export": "Подробный экспорт",
"edit data": "Редактирование",
"empty all": "пусто все",
"Enter Name": "Введите имя",
"farthest range": "быстрый диапазон",
"fuel level": "уровень топлива",
"full tank": "Полный бак",
"internal compartments": "внутренние отсеки",
"jump range": "Дальность прыжка",
"mass lock factor": "Коэффициент гравитационного захвата",
"max mass": "Максимальная масса",
"minimum mass": "Минимальная масса",
"optimal mass": "Оптимальная масса",
"net cost": "разница в цене",
"none created": "не создано",
"refuel time": "Время дозаправки",
"retrofit from": "модификация от",
"T-Load": "Тепл.",
"utility mounts": "Вспомогательное оборудование",
"about": "О сайте",
"action": "Действие",
"added": "Добавлено",
"available": "доступно",
"backup": "Резервная копия",
"bins": "контейнеры",
"build": "сборка",
"builds": "сборки",
"buy": "купить",
"cancel": "отменить",
"cargo": "Груз",
"close": "закрыть",
"compare": "сравнить ",
"comparison": "сравнение",
"comparisons": "сравнения",
"cost": "Стоимость",
"create": "создать",
"credits": "Кредиты",
"delete": "Удалить",
"dep": "Вып",
"deployed": "Открыты",
"disabled": "Отключено",
"discount": "Скидка",
"DPS": "УВС",
"efficiency": "Эффективность",
"empty": "пусто",
"ENG": "ДВГ",
"export": "Экспорт",
"forum": "Форум",
"fuel": "Топл.",
"hardpoints": "Орудийные порты",
"hull": "Корпус",
"import": "импортировать ",
"insurance": "Страховка",
"jumps": "Прыжков",
"laden": "Груж",
"language": "Язык",
"maneuverability": "Маневренность",
"max": "Макс",
"no": "Нет",
"pen": "ПБ",
"permalink": "Постоянная ссылка",
"pri": "Осн",
"proceed": "продолжить",
"PWR": "Эн",
"rate": "скорость",
"rename": "Переименовать",
"repair": "Починка",
"ret": "Убр",
"retracted": "Убрано",
"ROF": "В\/сек",
"save": "Сохранить",
"sell": "Продать",
"settings": "Настройки",
"shields": "Щиты",
"No Shield": "Нет щита",
"Never": "Никогда",
"ship": "Корабль",
"ships": "Корабли",
"shortened": "Укороченный",
"size": "размер",
"skip": "пропустить",
"Stock": "Стандартная комплектация",
"SYS": "СИС",
"time": "Время",
"type": "Тип",
"unladen": "Пуст",
"URL": "Ссылка",
"WEP": "ОРУ",
"yes": "Да",
"crew": "экипаж",
"jump": "прыж",
"pax": "псж",
"RST": "СБР",
"grade": "уровень",
"total laden": "всего груж",
"total unladen": "всего пуст",
"module": "модуль",
"announcements": "объявления",
"resistance": "сопротивление",
"Lightweight Alloy": "Легкие сплавы",
"base": "базовые",
"core module classes": "основные модули",
"Group highlighted ships": "Сгруппировать выделенные корабли",
"tooltips": "всплывающие подсказки",
"module resistances": "сопротивление модулей",
"power plant": "силовая установка",
"thrusters": "маневровые двигатели",
"frame shift drive": "рамочно-сместительный двигатель",
"life support": "система жизнеобеспечения",
"power distributor": "распределитель питания",
"sensors": "сенсоры",
"fuel tank": "топливный бак",
"resting heat (Beta)": "тепло покоя (бета)",
"hull hardness": "Прочность корпуса",
"weapon": "оружие",
"maximum speed": "максимальная скорость",
"maximum range": "максимальная дальность",
"shortlink": "короткая ссылка",
"guardian": "стражи",
"engineers": "инженеры",
"component": "компонент",
"amount": "кол-во",
"core internal": "основное оборуднование",
"optional internal": "доп. оборудование",
"Heat Sink Launcher": "Теплоотводная катапульта",
"scanners": "сканеры",
"experimental": "экспериментальное",
"mining": "шахтерство",
"lasers": "лазеры",
"ordnance": "артиллерия",
"projectiles": "с боеприпасами",
"hangars": "ангары",
"limpet controllers": "контроллеры снарядов",
"passenger cabins": "каюты пассажиров",
"structural reinforcement": "структурные усиления",
"flight assists": "помощники в полете",
"modifications": "модификации",
"wep_reload": "перезарядка",
"optimal multiplier": "оптимальный усилитель",
"Cargo Hatch": "Грузовой люк",
"Chaff Launcher": "Разбрасыватель дипольных отражателей",
"Point Defence": "Точечная оборона",
"Electronic Countermeasure": "Электронное противодействие",
"Xeno Scanner": "Сканер «инопланетянин»",
"Shutdown Field Neutraliser": "Нейтрализатор глушащего поля",
"Disruptor": "Диверсант",
"Pacifier": "Миротворец",
"Advanced Plasma Accelerator": "Улучшенный ускоритель плазмы",
"Cytoscrambler": "Дезинтегратор",
"Retributor": "Каратель",
"Enforcer": "Убийца",
"Imperial Hammer": "Имперский молот",
"Rocket Propelled FSD Disruptor": "Ракетный FSD-разрушитель",
"Pack-Hound": "Гончие",
"Shock Mine Launcher": "Установщик шоковых мин",
"Mining Lance": "Копье шахтера",
"Corrosion Resistant": "Коррозийно-устойчивый стеллаж",
"Standard Docking Computer": "Стандартный стыковочный компьютер",
"Advanced Docking Computer": "Улучшенный стыковочный компьютер",
"Detailed Surface Scanner": "Подробный сканер поверхности",
"Supercruise Assist": "Помощь в гиперкрейсерском режиме",
"Guardian Power Distributor": "Рапределитель питания Стражей",
"Enhanced Performance": "Усиленные маневровые двигатели",
"Guardian Hybrid Power Plant": "Гибридная силовая установка Стражей",
"Reinforced Alloy": "Укрепленные сплавы",
"Military Grade Composite": "Композит военного класса",
"Mirrored Surface Composite": "Композит с зеркальной поверхностью",
"Reactive Surface Composite": "Композит с реактивной поверхностью",
"Proto Light Alloys": "Опытные легкие сплавы",
"Ammo capacity": "Вместимость магазина",
"Lightweight": "Облегченный",
"Reinforced": "Усиленный",
"Shielded": "Защищенный",
"Fast scan": "Быстрое сканирование",
"Long range": "Дальнего действия",
"Wide angle": "Широкоугольный",
"Blast resistant": "Взрывостойкий",
"Heavy duty": "Надежный",
"Kinetic resistant": "Противокинетический",
"Resistance augmented": "С универсальной защитой",
"Thermal resistant": "Термостойкий",
"Thermo Block": "Блокировка тепла",
"Force Block": "Усиленная блокировка",
"Blast Block": "Блокировка взрыва",
"Flow Control": "Контроль интенсивности",
"Double Braced": "Двойная прочность",
"Super Capacitors": "Суперконденсаторы",
"Efficient": "Эффективный",
"Focused": "Точный",
"Overcharged": "Усиленный",
"Rapid fire": "Скорострельный",
"Short range": "Ближнего действия",
"Sturdy": "Прочный",
"Oversized": "Сверхразмер",
"Stripped Down": "Урезанный вариант",
"Phasing sequence": "Последовательность фазирования",
"Concordant sequence": "Последовательность координации",
"Scramble spectrum": "Отключающая сетка",
"Thermal shock": "Тепловой удар",
"Emissive munitions": "Эмиссионные припасы",
"Multi-servos": "Сервосистема",
"Inertial impact": "Инерционный удар",
"Thermal vent": "Теплоотдача",
"Regeneration sequence": "Последовательность восстановления",
"Thermal conduit": "Проводник тепла",
"High capacity": "Вместительный магазин",
"Incendiary rounds": "Зажигательные припасы",
"Auto loader": "Автоматическая система заряжения",
"Smart rounds": "Умные боеприпасы",
"Corrosive shell": "Разъедающие припасы",
"Force shell": "Усиленные снаряды",
"High yield shell": "Снаряд большой мощности",
"Dispersal field": "Рассеивающее поле",
"Thermal cascade": "Термический залп",
"Double shot": "Двойной выстрел",
"Dazzle shell": "Ослепляющие снаряды",
"Screening shell": "Заслоняющие снаряды",
"Drag munitions": "Замедляющие боеприпасы",
"Target lock breaker": "Генератор помех для системы захвата цели",
"Plasma Slug": "Плазменный рельсовый снаряд",
"Feedback Cascade": "Ответный запл",
"Super Penetrator": "Модуль сверхпробития",
"Overload munitions": "Вызывающие перезагрузку боеприпасы",
"Penetrator payload": "Бронебойные снаряды",
"Penetrator Payload": "Бронебойные снаряды",
"FSD interrupt": "Помеха для FSD",
"Penetrator Munitions": "Бронебойные боеголовки",
"Mass lock munition": "Боеприпасы с гравитационным захватом",
"Mass Lock Munition": "Боеприпасы с гравитационным захватом",
"Reverberating cascade": "Отраженный залп",
"Shift-lock canister": "Рамоблокирующая кассета",
"Ion disruptor": "Ионный дестабилизатор",
"Radiant Canister": "Светящаяся кассета",
"Expanded capture arc": "Расширенная дуга захвата",
"Enhanced low power": "Улучшенное энергосбережение",
"Fast Charge": "Быстрый заряд",
"Multi-weave": "Мультипрошивка",
"Hi-Cap": "Высокая ёмкость",
"Lo-draw": "Пониженное потребление",
"Rapid charge": "Быстрая зарядка",
"Specialised": "Адаптивный",
"Boss Cells": "Босс-ячейки",
"Recycling Cell": "Рециркуляционная ячейка",
"Reflective Plating": "Отражающая броня",
"Angled Plating": "Угловая броня",
"Layered Plating": "Многослойная броня",
"Deep Plating": "Утолщенная броня",
"Expanded Probe Scanning Radius": "Увеличение радиуса сканирования зондов",
"High charge capacity": "Высокоёмкий",
"Charge enhanced": "Быстрозаряжающийся",
"Engine focused": "Фокус на двигатель",
"System focused": "Фокус на систему",
"Weapon focused": "Фокус на орудия",
"Super Conduits": "Сверхпроводники",
"Cluster Capacitors": "Кассетные конденсаторы",
"Increased range": "Увеличенная дальность",
"Faster boot sequence": "Ускорение запуска",
"Deep Charge": "Заряд повышенной мощности",
"Thermal Spread": "Рассеивание тепла",
"Mass Manager": "Распределитель гравитации",
"Dirty": "«Грязная» донастройка",
"Clean": "«Чистая» донастройка",
"Drag Drives": "Ускорители",
"Drive Distributors": "Распределители тяги",
"Armoured": "Бронированный",
"Low emissions": "Малое излучение",
"Monstered": "Монстрация",
"roles": "роли",
"Maximize Jump Range": "Максимизировать дальность прыжка",
"Multi-purpose": "Многоцелевой",
"Combat": "Боец",
"Trader": "Торговец",
"Explorer": "Исследователь",
"Planetary Explorer": "Планетарный исследователь",
"Miner": "Шахтер",
"Racer": "Гонщик",
"Aberrant Shield Pattern Analysis": "Анализ аномального поведения щита",
"Abnormal Compact Emissions Data": "Аномальные компактные данные об излучении",
"Aerogel": "Аэрогель",
"Adaptive Encryptors Capture": "Захват адаптивного шифровальщика",
"Anomalous Bulk Scan Data": "Аномальный массив данных сканирования",
"Anomalous FSD Telemetry": "Аномальная телеметрия FSD",
"Antimony": "Сурьма",
"Arsenic": "Мышьяк",
"Atypical Disrupted Wake Echoes": "Атипичное эхо поврежденного следа",
"Atypical Encryption Archives": "Нетипичные архивы шифрования",
"Basic Conductors": "Простые проводники",
"Bio-Mechanical Conduits": "Биомеханические энергопроводники",
"Biotech Conductors": "Биотехнические проводники",
"Boron": "Бор",
"Cadmium": "Кадмий",
"Carbon": "Углерод",
"Carbon Fibre Plating": "Углеволоконная броня",
"Chemical Distillery": "Оборудование для перегонки химикатов",
"Chemical Manipulators": "Манипуляторы для работы с химикатами",
"Chemical Processors": "Оборудование для химобработки",
"Chromium": "Хром",
"Classified Scan Databanks": "Засекреченные базы данных сканированоя",
"Classified Scan Fragment": "Засекреченные фрагменты данных сканирования",
"Compound Shielding": "Многоступенчатая защита",
"Conductive Ceramics": "Проводящая керамика",
"Conductive Components": "Проводящие компоненты",
"Conductive Polymers": "Проводящие полимеры",
"Configurable Components": "Настраиваемые компоненты",
"Core Dynamics Composites": "Композиты Core Dynamics",
"Cracked Industrial Firmware": "Взломанные промышленные микропрограммы",
"Datamined Wake Exceptions": "Исключения из глубинного анализа данных следа",
"Decoded Emission Data": "Расшифрованные данные об излучении",
"Distorted Shield Cycle Recordings": "Поврежденные цикличные записи щита",
"Divergent Scan Data": "Неформатные данные сканирования",
"Eccentric Hyperspace Trajectories": "Аномальные траектории в гиперпространстве",
"Electrochemical Arrays": "Электрохимические массивы",
"Exceptional Scrambled Emission Data": "Исключительные зашифрованные данные об излучении",
"Exquisite Focus Crystals": "Отборные фокусировочные кристаллы",
"Flawed Focus Crystals": "Поврежденные фокусировочные кристаллы",
"Focus Crystals": "Фокусировочные кристаллы",
"Galvanising Alloys": "Сплавы для гальванизации",
"Germanium": "Германий",
"Grid Resistors": "Наборные резисторы",
"Heat Conduction Wiring": "Теплопроводящие провода",
"Heat Dispersion Plate": "Теплорассеивающая пластина",
"Heat Exchangers": "Теплообменные агрегаты",
"Heat Vanes": "Тепловые заслонки",
"High Density Composites": "Высокоплотные композиты",
"Hybrid Capacitors": "Гибридные конденсаторы",
"Imperial Shielding": "Имперская защита",
"Improvised Components": "Кустарные компоненты",
"Inconsistent Shield Soak Analysis": "Неполный анализ поглощения щита",
"Iron": "Железо",
"Irregular Emission Data": "Нестандартные данные об излучении",
"Manganese": "Марганец",
"Mechanical Components": "Механические компоненты",
"Mechanical Equipment": "Механическое оборудование",
"Mechanical Scrap": "Механические отходы",
"Mercury": "Ртуть",
"Military Grade Alloys": "Сплавы военного назначения",
"Military Supercapacitors": "Военные суперконденсаторы",
"Modified Consumer Firmware": "Измененные пользовательские микропрограммы",
"Modified Embedded Firmware": "Измененные встроенные микропрограммы",
"Molybdenum": "Молибден",
"Nickel": "Никель",
"Niobium": "Ниобий",
"Open Symmetric Keys": "Открытые симметричные ключи",
"Pharmaceutical Isolators": "Фармацевтические изоляционные материалы",
"Phase Alloys": "Фазовые сплавы",
"Phosphorus": "Фосфор",
"Polymer Capacitors": "Полимерные конденсаторы",
"Precipitated Alloys": "Осажденные сплавы",
"Proprietary Composites": "Патентованные композиты",
"Proto Heat Radiators": "Прототипы теплоизлучателей",
"Proto Radiolic Alloys": "Сплавы для изготовления зондов",
"Refined Focus Crystals": "Обработанные фокусировочные кристаллы",
"Ruthenium": "Рутений",
"Salvaged Alloys": "Захваченные сплавы",
"Security Firmware Patch": "Обновление для защитной микропрограммы",
"Selenium": "Селениум",
"Shield Emitters": "Щитоизлучатели",
"Shielding Sensors": "Сенсоры системы экранирования",
"Specialised Legacy Firmware": "Специальные микропрограммы предыдущего поколения",
"Strange Wake Solutions": "Странные расчеты следа",
"Sulphur": "Сера",
"Tagged Encryption Codes": "Меченные шифровальные коды",
"Technetium": "Технеций",
"Tellurium": "Теллурий",
"Thermic Alloys": "Термические сплавы",
"Tin": "Олово",
"Tungsten": "Вольфрам",
"Unexpected Emission Data": "Неожиданные данные об излучении",
"Unidentified Scan Archives": "Неопознанные архивы сканирования",
"Untypical Shield Scans": "Нетипичные данные сканирования щитов",
"Unusual Encrypted Files": "Особые зашифрованные файлы",
"Vanadium": "Ванадий",
"Worn Shield Emitters": "Изношенные щитоизлучатели",
"Yttrium": "Иттрий",
"Zinc": "Цинк",
"Zirconium": "Цирконий"
}

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