Compare commits

..

72 Commits

Author SHA1 Message Date
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
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
Colin McLeod
b49eb06101 Use IDs for standard components instead of [class][rating]. Update tests accordingly 2016-06-06 00:07:21 -07:00
Colin McLeod
eeb4a86a13 Allow 'named' standard module (special case for bulkheads) 2016-06-05 00:56:09 -07:00
Colin McLeod
3a265b2804 Update version to 2.0.0. Update react to v15 2016-06-05 00:54:35 -07:00
Colin McLeod
5ebabd6e6e Tweak string interpolation based on React v15 changes 2016-06-05 00:54:02 -07:00
Colin McLeod
6e6c337bbb Use table to align settings. Fixes #163 2016-06-05 00:52:23 -07:00
Colin McLeod
31b56354e0 Update French translations 2016-06-05 00:51:41 -07:00
Colin McLeod
fd1adbe55c Update German translations 2016-06-05 00:51:19 -07:00
Colin McLeod
3d0259c304 Fix lint issues 2016-04-04 13:02:26 -07:00
Colin McLeod
6f1b86118e Bump beta version 2016-04-04 12:52:31 -07:00
Colin McLeod
b3fcfa7808 Improve scrolling overview of shipyard page 2016-04-04 12:52:05 -07:00
Colin McLeod
4ead80a37c Improve touch detection 2016-04-04 12:51:46 -07:00
Colin McLeod
72dc73e090 Include discounts on comparison page 2016-04-04 12:31:54 -07:00
Colin McLeod
bb19dc25c0 Autofill sensor bug fix 2016-04-04 12:31:27 -07:00
Colin McLeod
1729636657 Make boost speed 0 when unable to boost 2016-04-04 12:30:18 -07:00
Colin McLeod
92809d1d24 Fix scrolling and hover issues on tablet 2016-04-04 12:29:59 -07:00
Colin McLeod
4e81c828df Fix JEST mock issue 2016-03-20 00:36:21 -07:00
Colin McLeod
f5bd7d2ecb Lint fix 2016-03-20 00:35:40 -07:00
Colin McLeod
a989bd5b5d Include migrate.html with prod build 2016-03-19 23:28:02 -07:00
Colin McLeod
1bedbb1909 Take discount into consideration for module tool tip 2016-03-19 23:27:43 -07:00
Colin McLeod
9775e1c742 Refactor footer, UI tweaks 2016-03-19 23:27:26 -07:00
Colin McLeod
19228a9c56 Change hold alt messaging 2016-03-19 23:25:42 -07:00
Colin McLeod
363bf7fd2a Shipyard page changes - in progress 2016-03-10 12:29:19 -08:00
Colin McLeod
15748066c9 Bulkhead refactoring 2016-03-10 12:29:02 -08:00
Colin McLeod
c3fe0a0cef UI Tweaks 2016-03-10 12:28:35 -08:00
Colin McLeod
1544deb350 Update tests for eagle changes 2016-03-07 09:14:48 -08:00
Colin McLeod
f060eb1b62 Fix standalone router bugs 2016-03-06 22:39:33 -08:00
Colin McLeod
32795ea678 Hide ALT text for mobile 2016-03-06 22:39:17 -08:00
Colin McLeod
d744d15132 Tweak touch module select tooltip 2016-03-06 21:33:25 -08:00
Colin McLeod
bfcef18c02 Fix Cost section discount label 2016-03-06 20:24:48 -08:00
Colin McLeod
b04c2ed2f5 Fix thruster warning bug 2016-03-06 20:24:33 -08:00
Colin McLeod
f51998b1c2 Update German, French, and Russian translations 2016-03-06 17:22:42 -08:00
Colin McLeod
d7000bfebf Update test to work with Jest 0.9.0 2016-03-06 17:22:26 -08:00
Colin McLeod
3e2b3e33fb Add localstorage migration for iframe 2016-03-04 11:15:51 -08:00
Colin McLeod
87dd52c043 Tweak and refactor build/role preset functions 2016-03-03 19:58:47 -08:00
Colin McLeod
05bc8ebb93 Fix find lightest shield generator min mass bug 2016-03-03 19:58:08 -08:00
Colin McLeod
09b945d29e tweak comments 2016-03-03 19:57:41 -08:00
Colin McLeod
67b5d749df Add can mount 2016-03-03 19:55:52 -08:00
Colin McLeod
a19fd69d0b Tweak internal slot labels 2016-03-03 19:55:04 -08:00
Colin McLeod
cfb65396ab fix max speed/boost, use lightest FSD and fuel tank 2016-03-02 10:08:16 -08:00
Colin McLeod
a4a0f96502 Add fuel tank to optimize 2016-03-02 10:07:49 -08:00
Colin McLeod
1390339024 Hull reinforcement will always clobber, fix 2016-03-02 10:07:05 -08:00
Colin McLeod
43d19f1dbb Fix phrase case 2016-03-02 10:06:41 -08:00
Colin McLeod
c20439264a Tweak GA tracking 2016-03-02 10:06:28 -08:00
Colin McLeod
61c3941618 Tweak reload on update message positioning 2016-02-29 09:35:55 -08:00
Colin McLeod
da277e4eaa Bump Beta version 2 2016-02-29 09:28:50 -08:00
Colin McLeod
51d24a1105 Fix Planetary Vehicle Hangar typo. Fixes #153 2016-02-26 09:04:47 -08:00
Colin McLeod
138931c0cf fix deploy script 2016-02-24 13:23:30 -08:00
57 changed files with 2032 additions and 874 deletions

2
.gitignore vendored
View File

@@ -3,3 +3,5 @@ build
.DS_Store .DS_Store
*.log *.log
nginx.pid nginx.pid
.idea
/bin

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) ![Latest Release](https://img.shields.io/github/release/EDCD/coriolis.svg) [![Build Status](https://travis-ci.org/EDCD/coriolis.svg?branch=master)](https://travis-ci.org/EDCD/coriolis) [![Chat to us on Discord](https://img.shields.io/badge/Discord-EDCD%20%23coriolis-blue.svg?style=social)](https://discord.gg/0uwCh6R62aPRjk9w)
## About ## About
@@ -9,13 +8,11 @@ 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. Please [submit issues](https://github.com/EDCD/coriolis/issues), or better yet [pull requests](https://github.com/EDCD/coriolis/pulls) for any corrections or additions to the database or the code.
### Feature Requests, Suggestions & Bugs ### Feature Requests, Suggestions & Bugs
Chat to us on [HipChat](https://www.hipchat.com/gfYQiZcmy)! Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
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
@@ -29,7 +26,7 @@ See the [Data wiki](https://github.com/cmmcleod/coriolis-data/wiki) for details
## License ## License
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 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/). [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. The code (Javascript, CSS, HTML, and SVG files only) specificially for Coriolis.io is released under the MIT License.

View File

@@ -6,7 +6,8 @@
{ {
"name": "Coriolis.io", "name": "Coriolis.io",
"url": "http://localhost:3300/outfit/anaconda/48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA?bn=Test%20My%20Ship", "url": "http://localhost:3300/outfit/anaconda/48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA?bn=Test%20My%20Ship",
"code": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA", "old-code": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA",
"code": "4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA",
"shipId": "anaconda" "shipId": "anaconda"
} }
], ],

View File

@@ -2,31 +2,31 @@
{ {
"shipId": "anaconda", "shipId": "anaconda",
"buildName": "Imported Anaconda", "buildName": "Imported Anaconda",
"buildCode": "08E7E6E5E8E8E5C------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=", "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" "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", "shipId": "anaconda",
"buildName": "Imported Anaconda", "buildName": "Imported Anaconda",
"buildCode": "08E7E6E5E8E8E5C------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=", "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" "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", "shipId": "cobra_mk_iii",
"buildName": "Imported Cobra Mk III", "buildName": "Imported Cobra Mk III",
"buildCode": "04A4C4E3D2A3D4C1712222503040202490f242h.Iw1-kA==.Aw1-kA==", "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" "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", "shipId": "type_9_heavy",
"buildName": "Imported Type-9 Heavy", "buildName": "Imported Type-9 Heavy",
"buildCode": "35A7D6A5A4D4D5C7e2k2f2h110001020306054j03022f01242i.Iw18eQ==.Aw18eQ==", "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" "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", "shipId": "vulture",
"buildName": "Imported Vulture", "buildName": "Imported Vulture",
"buildCode": "44A5A4A3D5A4D3C1e1e0e0j04044a0n532jf1.Iw19kA==.Aw19kA==", "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)" "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 +1,50 @@
{ {
"type_6_transporter": { "type_6_transporter": {
"Cargo": "02A4D4A2D2D2D4C-----04040303430101.Iw1-kA==.Aw1-kA==", "Cargo": "0p0tdFal8d8s8f4-----04040303430101.Iw1-kA==.Aw1-kA==",
"Miner": "03A4D4A2D2D2D4C2l2l---040403451q0101.Iw1-kA==.Aw1-kA==", "Miner": "0p5tdFal8d8s8f42l2l---040403451q0101.Iw1-kA==.Aw1-kA==",
"Hopper": "02A4D4A2D1A2D4C1717---030302024300-.Iw1-kA==.Aw1-kA==" "Hopper": "0p0tdFal8d0s8f41717---030302024300-.Iw1-kA==.Aw1-kA=="
}, },
"type_7_transport": { "type_7_transport": {
"Cargo": "02A5D5A4D3D3D5C--------0505040403480101.Iw18aQ==.Aw18aQ==", "Cargo": "0p0tiFfliddsdf5--------0505040403480101.Iw18aQ==.Aw18aQ==",
"Miner": "04D5D5A4D2D3D5C--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ==" "Miner": "0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ=="
}, },
"federal_dropship": { "federal_dropship": {
"Cargo": "04D5D5A5D3D4D4C-1717------05040448020201.Iw18aQ==.Aw18aQ==" "Cargo": "0pdtiFflnddsif4-1717------05040448020201.Iw18aQ==.Aw18aQ=="
}, },
"asp": { "asp": {
"Miner": "25A5A5A4D4A5A5C0s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ==" "Miner": "2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ=="
}, },
"imperial_clipper": { "imperial_clipper": {
"Cargo": "03A5D5A5D4D5D4C--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==", "Cargo": "0p5tiFflndisnf4--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==",
"Dream": "26A6A5A5D6A5A4C0v0v0s0s0404040n4k5n5d2b29292o-.Iw18aQ==.Aw18aQ==", "Dream": "2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o-.Iw18aQ==.Aw18aQ==",
"Current": "04A6A5A5D4A5A4C----------------.Iw18aQ==.Aw18aQ==" "Current": "0patkFflndfskf4----------------.Iw18aQ==.Aw18aQ=="
}, },
"type_9_heavy": { "type_9_heavy": {
"Current": "04A7D6A5D5D4D6C---------0706054a0303020224.Iw18eQ==.Aw18eQ==" "Current": "0patsFklndnsif6---------0706054a0303020224.Iw18eQ==.Aw18eQ=="
}, },
"python": { "python": {
"Cargo": "04A6D5A4D6D6D5C---------050505040448020201.Iw18eQ==.Aw18eQ==", "Cargo": "0patnFflidsssf5---------050505040448020201.Iw18eQ==.Aw18eQ==",
"Miner": "06A6A5A4D6A6A5C0v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.Aw18eQ==", "Miner": "0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.Aw18eQ==",
"Dream": "27A6A5A4D7A6A5C0v0v0v27270404040m5n5n4f2d2d032t0201.Iw18eQ==.Aw18eQ==", "Dream": "2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201.Iw18eQ==.Aw18eQ==",
"Missile": "07E6E5E4E7E6E5C2f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ==" "Missile": "0pttoFjljdystf52f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ=="
}, },
"anaconda": { "anaconda": {
"Dream": "48A7A6A5D8A8A5C2c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d040303326b.Iw18ZlA=.Aw18ZlA=", "Dream": "4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d040303326b.Iw18ZlA=.Aw18ZlA=",
"Cargo": "04A6D6A5D5D8D5C----------------0605050504040445030301.Iw18ZlA=.Aw18ZlA=", "Cargo": "0patnFklndnsxf5----------------0605050504040445030301.Iw18ZlA=.Aw18ZlA=",
"Current": "04A6D6A5D5A8D5C----------------0605050504040403034524.Iw18ZlA=.Aw18ZlA=", "Current": "0patnFklndksxf5----------------0605050504040403034524.Iw18ZlA=.Aw18ZlA=",
"Explorer": "04A6D6A5D5A8D5C--------0202------f7050505040s372f2i4524.Iw18ZlA=.Aw18ZlA=", "Explorer": "0patnFklndksxf5--------0202------f7050505040s372f2i4524.Iw18ZlA=.Aw18ZlA=",
"Test": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.Iw18ZlA=.Aw18ZlA=" "Test": "4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.Iw18ZlA=.Aw18ZlA="
}, },
"diamondback_explorer": { "diamondback_explorer": {
"Explorer": "02A4D5A3D3D3D5C---0202--320p432i2f.Iw1-kA==.Aw1-kA==" "Explorer": "0p0tdFfldddsdf5---0202--320p432i2f.Iw1-kA==.Aw1-kA=="
}, },
"vulture": { "vulture": {
"Bounty Hunter": "34A4C4A3D5A4A3C1e1e0404-0l4a5d27662j.Iw19kA==.Aw19kA==" "Bounty Hunter": "3patcFalddksff31e1e0404-0l4a5d27662j.Iw19kA==.Aw19kA=="
}, },
"fer_de_lance": { "fer_de_lance": {
"Attack": "25A5C4A4D6A4A3C1r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.Aw18aQ==" "Attack": "2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.Aw18aQ=="
}, },
"eagle": { "eagle": {
"Figther": "42A3A3A1D2A2A2C0p0p24-40532j.Iw19A===.Aw19A===" "Figther": "4p0t5F5l3d5s5f20p0p24-40532j-.Iw1-EA==.Aw1-EA=="
} }
} }

View File

@@ -3348,8 +3348,8 @@
"references": [ "references": [
{ {
"name": "Coriolis.io", "name": "Coriolis.io",
"url": "http://localhost:3300/outfit/eagle/42A3A3A1D2A2A2C0p0p24-40532j.Iw19A===.Aw19A===?bn=Figther", "url": "http://localhost:3300/outfit/eagle/42A3A3A1D2A2A2C0p0p24-40532j-.Iw1-EA==.Aw1-EA==?bn=Figther",
"code": "42A3A3A1D2A2A2C0p0p24-40532j.AwRj49iA.AwgsIkEZigmIA===", "code": "42A3A3A1D2A2A2C0p0p24-40532j-.Iw1-EA==.Aw1-EA==",
"shipId": "eagle" "shipId": "eagle"
} }
], ],

View File

@@ -10,7 +10,7 @@ import { getLanguage } from '../src/app/i18n/Language';
describe('Import Modal', function() { describe('Import Modal', function() {
let MockRouter = require('../src/app/Router'); let MockRouter = require('../src/app/Router').default;
const Persist = require('../src/app/stores/Persist').default; const Persist = require('../src/app/stores/Persist').default;
const ModalImport = require('../src/app/components/ModalImport').default; const ModalImport = require('../src/app/components/ModalImport').default;
const mockContext = { const mockContext = {
@@ -25,8 +25,6 @@ describe('Import Modal', function() {
onWindowResize: jest.genMockFunction() onWindowResize: jest.genMockFunction()
}; };
MockRouter.go = jest.genMockFunction();
let modal, render, ContextProvider = Utils.createContextProvider(mockContext); let modal, render, ContextProvider = Utils.createContextProvider(mockContext);
/** /**
@@ -110,7 +108,8 @@ describe('Import Modal', function() {
it('catches an invalid backup', function() { it('catches an invalid backup', function() {
const importData = require('./fixtures/valid-backup'); const importData = require('./fixtures/valid-backup');
let invalidImportData = Object.assign({}, importData); let invalidImportData = Object.assign({}, importData);
invalidImportData.builds.asp = null; // Remove Asp Miner build used in comparison //invalidImportData.builds.asp = null; // Remove Asp Miner build used in comparison
delete(invalidImportData.builds.asp);
pasteText('"this is not valid"'); pasteText('"this is not valid"');
expect(modal.state.importValid).toBeFalsy(); expect(modal.state.importValid).toBeFalsy();
@@ -140,9 +139,10 @@ describe('Import Modal', function() {
expect(modal.state.importValid).toBeTruthy(); expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null); expect(modal.state.errorMsg).toEqual(null);
expect(modal.state.singleBuild).toBe(true);
clickProceed(); clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1); expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda/48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA?bn=Test%20My%20Ship'); 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() { it('catches an invalid build', function() {

View File

@@ -2,7 +2,7 @@ import Ship from '../src/app/shipyard/Ship';
import { Ships } from 'coriolis-data/dist'; import { Ships } from 'coriolis-data/dist';
import * as ModuleUtils from '../src/app/shipyard/ModuleUtils'; import * as ModuleUtils from '../src/app/shipyard/ModuleUtils';
describe("Ship Factory", function() { describe("Ship", function() {
it("can build all ships", function() { it("can build all ships", function() {
for (let s in Ships) { for (let s in Ships) {
@@ -16,7 +16,7 @@ describe("Ship Factory", function() {
ship.buildWith(shipData.defaults); ship.buildWith(shipData.defaults);
expect(ship.totalCost).toEqual(shipData.retailCost, s + ' retail cost does not match default build cost'); expect(ship.totalCost).toEqual(shipData.retailCost, s + ' retail cost does not match default build cost');
expect(ship.cargoCapacity).toBeDefined(s + ' cargo'); expect(ship.cargoCapacity).toBeDefined();
expect(ship.priorityBands[0].retracted).toBeGreaterThan(0, s + ' priorityBands'); expect(ship.priorityBands[0].retracted).toBeGreaterThan(0, s + ' priorityBands');
expect(ship.powerAvailable).toBeGreaterThan(0, s + ' powerAvailable'); expect(ship.powerAvailable).toBeGreaterThan(0, s + ' powerAvailable');
expect(ship.unladenRange).toBeGreaterThan(0, s + ' unladenRange'); expect(ship.unladenRange).toBeGreaterThan(0, s + ' unladenRange');
@@ -115,8 +115,7 @@ describe("Ship Factory", function() {
anaconda.use(anaconda.internal[1], ModuleUtils.internal('4j')); // 6E Shield Generator anaconda.use(anaconda.internal[1], ModuleUtils.internal('4j')); // 6E Shield Generator
expect(anaconda.internal[2].c).toEqual(null, 'Anaconda default shield generator slot is empty'); expect(anaconda.internal[2].m).toEqual(null, 'Anaconda default shield generator slot is empty');
expect(anaconda.internal[2].m).toEqual(null, 'Anaconda default shield generator slot id is null');
expect(anaconda.internal[1].m.id).toEqual('4j', 'Slot 1 should have SG 4j in it'); 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'); expect(anaconda.internal[1].m.grp).toEqual('sg','Slot 1 should have SG 4j in it');
@@ -133,8 +132,7 @@ describe("Ship Factory", function() {
anaconda.use(anaconda.internal[3], ModuleUtils.internal('32')); anaconda.use(anaconda.internal[3], ModuleUtils.internal('32'));
expect(anaconda.internal[4].c).toEqual(null, 'Anaconda original fuel scoop slot is empty'); expect(anaconda.internal[4].m).toEqual(null, 'Anaconda original fuel scoop slot is empty');
expect(anaconda.internal[4].m).toEqual(null, 'Anaconda original fuel scoop slot id is null');
expect(anaconda.internal[3].m.id).toEqual('32', 'Slot 1 should have FS 32 in it'); 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'); expect(anaconda.internal[3].m.grp).toEqual('fs','Slot 1 should have FS 32 in it');
}); });
@@ -150,8 +148,7 @@ describe("Ship Factory", function() {
anaconda.use(anaconda.internal[3], ModuleUtils.internal('23')); anaconda.use(anaconda.internal[3], ModuleUtils.internal('23'));
expect(anaconda.internal[4].c).toEqual(null, 'Anaconda original refinery slot is empty'); expect(anaconda.internal[4].m).toEqual(null, 'Anaconda original refinery slot is empty');
expect(anaconda.internal[4].m).toEqual(null, 'Anaconda original refinery slot id is null');
expect(anaconda.internal[3].m.id).toEqual('23', 'Slot 1 should have RF 23 in it'); 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'); expect(anaconda.internal[3].m.grp).toEqual('rf','Slot 1 should have RF 23 in it');
}); });

View File

@@ -11,6 +11,15 @@ new WebpackDevServer(webpack(config), {
// For some reason connect-history-api-fallback does not allow '.' in the URL for history fallback... // For some reason connect-history-api-fallback does not allow '.' in the URL for history fallback...
{ from: /\/outfit\//, to: '/index.html' } { from: /\/outfit\//, to: '/index.html' }
] ]
},
stats: {
assets: true,
colors: true,
version: false,
hash: false,
timings: true,
chunks: false,
chunkModules: false
} }
}).listen(3300, "0.0.0.0", function (err, result) { }).listen(3300, "0.0.0.0", function (err, result) {
if (err) { if (err) {

View File

@@ -1,12 +1,12 @@
{ {
"name": "coriolis_shipyard", "name": "coriolis_shipyard",
"version": "2.0.0-Beta-1", "version": "2.2.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.edcd.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.0.0",
"license": "MIT", "license": "MIT",
@@ -20,18 +20,17 @@
"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 && NODE_ENV=production webpack -d -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:prod && npm run rsync" "deploy": "npm run lint && npm test && npm run build && npm run rsync"
}, },
"jest": { "jest": {
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest", "scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
"testFileExtensions": [ "testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$",
"js"
],
"moduleFileExtensions": [ "moduleFileExtensions": [
"js", "js",
"json", "json",
"jsx" "jsx"
], ],
"automock": true,
"unmockedModulePathPatterns": [ "unmockedModulePathPatterns": [
"<rootDir>/node_modules/react", "<rootDir>/node_modules/react",
"<rootDir>/node_modules/react-dom", "<rootDir>/node_modules/react-dom",
@@ -43,7 +42,7 @@
"<rootDir>/node_modules/d3", "<rootDir>/node_modules/d3",
"<rootDir>/node_modules/lz-string", "<rootDir>/node_modules/lz-string",
"<rootDir>/node_modules/jsen", "<rootDir>/node_modules/jsen",
"<rootDir>/node_modules/coriolis-data", "coriolis-data",
"<rootDir>/src/app/shipyard", "<rootDir>/src/app/shipyard",
"<rootDir>/src/app/i18n", "<rootDir>/src/app/i18n",
"<rootDir>/src/app/utils", "<rootDir>/src/app/utils",
@@ -68,13 +67,13 @@
"extract-text-webpack-plugin": "^0.9.1", "extract-text-webpack-plugin": "^0.9.1",
"file-loader": "^0.8.4", "file-loader": "^0.8.4",
"html-webpack-plugin": "^1.7.0", "html-webpack-plugin": "^1.7.0",
"jest-cli": "*", "jest-cli": "^16.0.1",
"jsen": "^0.6.0", "jsen": "^0.6.0",
"json-loader": "^0.5.3", "json-loader": "^0.5.3",
"less": "^2.5.3", "less": "^2.5.3",
"less-loader": "^2.2.1", "less-loader": "^2.2.1",
"react-addons-test-utils": "^0.14.7", "react-addons-test-utils": "^15.0.1",
"react-testutils-additions": "^0.16.0", "react-testutils-additions": "^15.1.0",
"rimraf": "^2.4.3", "rimraf": "^2.4.3",
"style-loader": "^0.13.0", "style-loader": "^0.13.0",
"url-loader": "^0.5.6", "url-loader": "^0.5.6",
@@ -84,12 +83,12 @@
"dependencies": { "dependencies": {
"babel-polyfill": "*", "babel-polyfill": "*",
"classnames": "^2.2.0", "classnames": "^2.2.0",
"coriolis-data": "cmmcleod/coriolis-data", "coriolis-data": "EDCD/coriolis-data",
"d3": "^3.5.9", "d3": "3.5.16",
"fbemitter": "^2.0.0", "fbemitter": "^2.0.0",
"lz-string": "^1.4.4", "lz-string": "^1.4.4",
"react": "^0.14.7", "react": "^15.0.1",
"react-dom": "^0.14.7", "react-dom": "^15.0.1",
"superagent": "^1.4.0" "superagent": "^1.4.0"
} }
} }

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

@@ -21,17 +21,18 @@ import ErrorDetails from './pages/ErrorDetails';
export default class Coriolis extends React.Component { export default class Coriolis extends React.Component {
static childContextTypes = { static childContextTypes = {
language: React.PropTypes.object.isRequired,
sizeRatio: React.PropTypes.number.isRequired,
route: React.PropTypes.object.isRequired,
openMenu: React.PropTypes.func.isRequired,
closeMenu: React.PropTypes.func.isRequired, closeMenu: React.PropTypes.func.isRequired,
showModal: React.PropTypes.func.isRequired,
hideModal: React.PropTypes.func.isRequired, hideModal: React.PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired, language: React.PropTypes.object.isRequired,
termtip: React.PropTypes.func.isRequired, noTouch: React.PropTypes.bool.isRequired,
onCommand: React.PropTypes.func.isRequired,
onWindowResize: React.PropTypes.func.isRequired, onWindowResize: React.PropTypes.func.isRequired,
onCommand: React.PropTypes.func.isRequired openMenu: React.PropTypes.func.isRequired,
route: React.PropTypes.object.isRequired,
showModal: React.PropTypes.func.isRequired,
sizeRatio: React.PropTypes.number.isRequired,
termtip: React.PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired
}; };
/** /**
@@ -54,9 +55,10 @@ export default class Coriolis extends React.Component {
this.emitter = new EventEmitter(); this.emitter = new EventEmitter();
this.state = { this.state = {
noTouch: !('ontouchstart' in window || navigator.msMaxTouchPoints || navigator.maxTouchPoints),
page: null, page: null,
language: getLanguage(Persist.getLangCode()), language: getLanguage(Persist.getLangCode()),
route: null, route: {},
sizeRatio: Persist.getSizeRatio() sizeRatio: Persist.getSizeRatio()
}; };
@@ -237,17 +239,18 @@ export default class Coriolis extends React.Component {
*/ */
getChildContext() { getChildContext() {
return { return {
language: this.state.language,
route: this.state.route,
sizeRatio: this.state.sizeRatio,
openMenu: this._openMenu,
closeMenu: this._closeMenu, closeMenu: this._closeMenu,
showModal: this._showModal,
hideModal: this._hideModal, hideModal: this._hideModal,
tooltip: this._tooltip, language: this.state.language,
termtip: this._termtip, noTouch: this.state.noTouch,
onCommand: this._onCommand,
onWindowResize: this._onWindowResize, onWindowResize: this._onWindowResize,
onCommand: this._onCommand openMenu: this._openMenu,
route: this.state.route,
showModal: this._showModal,
sizeRatio: this.state.sizeRatio,
termtip: this._termtip,
tooltip: this._tooltip
}; };
} }
@@ -266,7 +269,7 @@ export default class Coriolis extends React.Component {
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.body.addEventListener('scroll', () => this._tooltip()); document.getElementById('coriolis').addEventListener('scroll', () => this._tooltip());
document.addEventListener('keydown', this._keyDown); document.addEventListener('keydown', this._keyDown);
Persist.addListener('language', this._onLanguageChange); Persist.addListener('language', this._onLanguageChange);
Persist.addListener('sizeRatio', this._onSizeRatioChange); Persist.addListener('sizeRatio', this._onSizeRatioChange);
@@ -281,11 +284,16 @@ export default class Coriolis extends React.Component {
render() { render() {
let currentMenu = this.state.currentMenu; let currentMenu = this.state.currentMenu;
return <div onClick={this._closeMenu}> return <div style={{ minHeight: '100%' }} onClick={this._closeMenu} className={ this.state.noTouch ? 'no-touch' : null }>
<Header appCacheUpdate={this.state.appCacheUpdate} currentMenu={currentMenu} /> <Header appCacheUpdate={this.state.appCacheUpdate} currentMenu={currentMenu} />
{ this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) : <NotFoundPage/> } { this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) : <NotFoundPage/> }
{ this.state.modal } { this.state.modal }
{ this.state.tooltip } { this.state.tooltip }
<footer>
<div className="right cap">
<a href="https://github.com/EDCD/coriolis" target="_blank" title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
</div>
</footer>
</div>; </div>;
} }
} }

View File

@@ -1,15 +1,20 @@
import Persist from './stores/Persist'; import Persist from './stores/Persist';
let standalone = undefined;
/** /**
* Determine if the app is running in mobile/tablet 'standalone' mode * Determine if the app is running in mobile/tablet 'standalone' mode
* @return {Boolean} True if the app is in standalone mode * @return {Boolean} True if the app is in standalone mode
*/ */
function isStandAlone() { function isStandAlone() {
try { if (standalone === undefined) {
return window.navigator.standalone || (window.external && window.external.msIsSiteMode && window.external.msIsSiteMode()); try {
} catch (ex) { standalone = window.navigator.standalone || (window.external && window.external.msIsSiteMode && window.external.msIsSiteMode());
return false; } catch (ex) {
standalone = false;
}
} }
return standalone;
} }
/** /**
@@ -44,14 +49,14 @@ Router.start = function() {
if (isStandAlone()) { if (isStandAlone()) {
let state = Persist.getState(); let state = Persist.getState();
// If a previous state has been stored, load that state // If a previous state has been stored, load that state
if (state && state.name && state.params) { if (state && state.path) {
Router(this.props.initialPath || '/'); Router.replace(state.path, null, true);
} else { } else {
Router('/'); Router.replace('/', null, true);
} }
} else { } else {
let url = location.pathname + location.search; let url = location.pathname + location.search;
Router.replace(url, null, true, true); Router.replace(url, null, true);
} }
}; };
@@ -68,6 +73,9 @@ Router.go = function(path, state) {
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()) {
Persist.setState(ctx);
}
history.pushState(ctx.state, ctx.title, ctx.canonicalPath); history.pushState(ctx.state, ctx.title, ctx.canonicalPath);
} }
return ctx; return ctx;
@@ -85,7 +93,12 @@ Router.go = function(path, state) {
Router.replace = function(path, state, dispatch) { Router.replace = function(path, state, dispatch) {
gaTrack(path); gaTrack(path);
let ctx = new Context(path, state); let ctx = new Context(path, state);
if (dispatch) Router.dispatch(ctx); if (dispatch) {
Router.dispatch(ctx);
}
if (isStandAlone()) {
Persist.setState(ctx);
}
history.replaceState(ctx.state, ctx.title, ctx.canonicalPath); history.replaceState(ctx.state, ctx.title, ctx.canonicalPath);
return ctx; return ctx;
}; };
@@ -227,7 +240,7 @@ Route.prototype.match = function(path, params) {
*/ */
function gaTrack(path) { function gaTrack(path) {
if (window.ga) { if (window.ga) {
window.ga('send', 'pageview', { page: path }); window.ga('send', 'pageview', path);
} }
} }

View File

@@ -5,7 +5,7 @@ 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';
const PRESS_THRESHOLD = 5000; // mouse/touch down threshold const PRESS_THRESHOLD = 500; // mouse/touch down threshold
/** /**
* Available modules menu * Available modules menu
@@ -173,6 +173,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_touchStart(showDiff, event) { _touchStart(showDiff, event) {
event.preventDefault();
let rect = event.currentTarget.getBoundingClientRect(); let rect = event.currentTarget.getBoundingClientRect();
this.touchTimeout = setTimeout(showDiff.bind(this, rect), PRESS_THRESHOLD); this.touchTimeout = setTimeout(showDiff.bind(this, rect), PRESS_THRESHOLD);
} }
@@ -183,10 +184,10 @@ export default class AvailableModulesMenu extends TranslatedComponent {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_touchEnd(select, event) { _touchEnd(select, event) {
event.preventDefault();
if (this.touchTimeout !== null) { // If timeout has not fired (been nulled out) yet if (this.touchTimeout !== null) { // If timeout has not fired (been nulled out) yet
select(); select();
} }
event.preventDefault();
this._hideDiff(); this._hideDiff();
} }

View File

@@ -208,7 +208,7 @@ export default class BarChart extends TranslatedComponent {
<g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}> <g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}>
<text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}> <text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}>
<tspan>{title}</tspan> <tspan>{title}</tspan>
{ unit ? <tspan className='metric'>{` (${unit})`}</tspan> : null } { unit ? <tspan className='metric'> ({unit})</tspan> : null }
</text> </text>
</g> </g>
<g className='y axis' ref={(elem) => { let e = d3.select(elem); e.call(this.yAxis); e.selectAll('text').each(insertLinebreaks); }} /> <g className='y axis' ref={(elem) => { let e = d3.select(elem); e.call(this.yAxis); e.selectAll('text').each(insertLinebreaks); }} />

View File

@@ -304,8 +304,8 @@ export default class CostSection extends TranslatedComponent {
<tr className='main'> <tr className='main'>
<th colSpan='2' className='sortable le' onClick={this._sortCostBy.bind(this,'m')}> <th colSpan='2' className='sortable le' onClick={this._sortCostBy.bind(this,'m')}>
{translate('module')} {translate('module')}
{shipDiscount && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct(shipDiscount)}]`}</u>} {shipDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct(shipDiscount)}]`}</u> : null}
{moduleDiscount && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u>} {moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
</th> </th>
<th className='sortable le' onClick={this._sortCostBy.bind(this, 'cr')} >{translate('credits')}</th> <th className='sortable le' onClick={this._sortCostBy.bind(this, 'cr')} >{translate('credits')}</th>
</tr> </tr>
@@ -363,7 +363,7 @@ export default class CostSection extends TranslatedComponent {
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'buyName')}>{translate('buy')}</th> <th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'buyName')}>{translate('buy')}</th>
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'cr')}> <th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'cr')}>
{translate('net cost')} {translate('net cost')}
{moduleDiscount < 1 && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u>} {moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
</th> </th>
</tr> </tr>
</thead> </thead>
@@ -400,7 +400,7 @@ export default class CostSection extends TranslatedComponent {
let retrofitCosts = []; let retrofitCosts = [];
let retrofitTotal = 0, i, l, item; let retrofitTotal = 0, i, l, item;
if (ship.bulkheads.index != retrofitShip.bulkheads.index) { if (ship.bulkheads.m.index != retrofitShip.bulkheads.m.index) {
item = { item = {
buyClassRating: ship.bulkheads.m.class + ship.bulkheads.m.rating, buyClassRating: ship.bulkheads.m.class + ship.bulkheads.m.rating,
buyName: ship.bulkheads.m.name, buyName: ship.bulkheads.m.name,
@@ -606,6 +606,7 @@ export default class CostSection extends TranslatedComponent {
} }
if (nextProps.ship != this.props.ship || nextProps.code != this.props.code) { if (nextProps.ship != this.props.ship || nextProps.code != this.props.code) {
nextProps.ship.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
this._updateAmmoCosts(nextProps.ship); this._updateAmmoCosts(nextProps.ship);
this._updateRetrofit(nextProps.ship, retrofitShip); this._updateRetrofit(nextProps.ship, retrofitShip);
this._sortCost(nextProps.ship); this._sortCost(nextProps.ship);

View File

@@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import Slot from './Slot'; import Slot from './Slot';
import { DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret } from './SvgIcons';
/** /**
* Hardpoint / Utility Slot * Hardpoint / Utility Slot
@@ -33,24 +34,31 @@ export default class HardpointSlot extends Slot {
*/ */
_getSlotDetails(m, translate, formats, u) { _getSlotDetails(m, translate, formats, u) {
if (m) { if (m) {
let classRating = `${m.class}${m.rating}${m.mount ? '/' + m.mount : ''}${m.missile ? m.missile : ''}`; let classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
let { drag, drop } = this.props; let { drag, drop } = this.props;
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}> return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}> <div className={'cb'}>
<div className={'l'}>{classRating + ' ' + translate(m.name || m.grp)}</div> <div className={'l'}>
{m.mount && m.mount == 'F' ? <MountFixed /> : ''}
{m.mount && m.mount == 'G' ? <MountGimballed /> : ''}
{m.mount && m.mount == 'T' ? <MountTurret /> : ''}
{m.type && m.type == 'K' ? <DamageKinetic /> : ''}
{m.type && m.type == 'T' ? <DamageThermal /> : ''}
{m.type && m.type == 'KT' ? <span><DamageKinetic /><DamageThermal /></span> : ''}
{m.type && m.type == 'E' ? <DamageExplosive /> : ''}
{classRating} {translate(m.name || m.grp)}</div>
<div className={'r'}>{m.mass}{u.T}</div> <div className={'r'}>{m.mass}{u.T}</div>
</div> </div>
<div className={'cb'}> <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')}: {formats.round1(m.dps)} { m.clip ? <span>({formats.round1((m.clip * m.dps / m.rof) / ((m.clip / m.rof) + m.reload)) })</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.eps ? <div className={'l'}>{translate('EPS')}: {formats.round1(m.eps)} { m.clip ? <span>({formats.round1((m.clip * m.eps / m.rof) / ((m.clip / m.rof) + m.reload)) })</span> : null }</div> : null }
{ m.thermload ? <div className={'l'}>{translate('T-Load')}: {m.thermload}</div> : null } { m.hps ? <div className={'l'}>{translate('HPS')}: {formats.round1(m.hps)} { m.clip ? <span>({formats.round1((m.clip * m.hps / m.rof) / ((m.clip / m.rof) + m.reload)) })</span> : null }</div> : null }
{ m.type ? <div className={'l'}>{translate('type')}: {m.type}</div> : null } { m.dps && m.eps ? <div className={'l'}>{translate('DPE')}: {formats.round1(m.dps / m.eps)}</div> : null }
{ m.rof ? <div className={'l'}>{translate('ROF')}: {m.rof}{u.ps}</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.range && !m.dps ? <div className={'l'}>{translate('Range')} : {formats.round(m.range / 1000)}{u.km}</div> : null }
{ m.shieldmul ? <div className={'l'}>+{formats.rPct(m.shieldmul)}</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 }
{ m.ammo >= 0 ? <div className={'l'}>{translate('ammo')}: {formats.int(m.clip)}+{formats.int(m.ammo)}</div> : null }
</div> </div>
</div>; </div>;
} else { } else {

View File

@@ -94,6 +94,7 @@ export default class HardpointsSlotSection extends SlotSection {
return <div className='select hardpoint' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}> return <div className='select hardpoint' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul> <ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li> <li className='lc' onClick={this._empty}>{translate('empty all')}</li>
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('pl')}</div> <div className='select-group cap'>{translate('pl')}</div>
<ul> <ul>

View File

@@ -362,37 +362,51 @@ export default class Header extends TranslatedComponent {
return ( return (
<div className='menu-list no-wrap cap' onClick={ (e) => e.stopPropagation() }> <div className='menu-list no-wrap cap' onClick={ (e) => e.stopPropagation() }>
<div style={{ lineHeight: '2em' }}> <table style={{ width: '100%', background: 'none', borderSpacing: '0 0.5em' }}>
{translate('language')} <tbody>
<select className='cap' value={Persist.getLangCode()} onChange={this._setLanguage}> <tr>
{this.languageOptions} <td>{translate('language')}</td>
</select> <td className='ri'>
<br/> <select className='cap' dir='rtl' value={Persist.getLangCode()} onChange={this._setLanguage}>
<span className='cap ptr' onClick={this._toggleTooltips} > {this.languageOptions}
{translate('tooltips')} </select>
<div className={cn({ disabled: !tips, 'primary-disabled': tips })} style={{ marginLeft: '0.5em', display: 'inline-block' }}>{(tips ? '✓' : '✗')}</div> </td>
</span> </tr>
<br/> <tr className='cap ptr' onClick={this._toggleTooltips} >
{translate('insurance')} <td>{translate('tooltips')}</td>
<select className='cap' value={Persist.getInsurance()} onChange={this._setInsurance}> <td className={cn('ri', { disabled: !tips, 'primary-disabled': tips })}>{(tips ? '✓' : '✗')}</td>
{this.insuranceOptions} </tr>
</select> <tr>
<br/> <td>{translate('insurance')}</td>
{translate('ship')} {translate('discount')} <td className='ri'>
<input type='text' size='10' value={this.state.shipDiscount} onChange={this._changeShipDiscount} onFocus={selectAll} onBlur={this._setShipDiscount} onKeyDown={this._kpShipDiscount}/> <select className='cap' dir='rtl' value={Persist.getInsurance()} onChange={this._setInsurance}>
<u className='primary-disabled'>%</u> {this.insuranceOptions}
<br/> </select>
{translate('module')} {translate('discount')} </td>
<input type='text' size='10' value={this.state.moduleDiscount} onChange={this._changeModuleDiscount} onFocus={selectAll} onBlur={this._setModuleDiscount} onKeyDown={this._kpModuleDiscount}/> </tr>
<u className='primary-disabled'>%</u> <tr>
</div> <td>{translate('ship')} {translate('discount')}</td>
<td className='ri'>
<input type='text' size='10' value={this.state.shipDiscount} onChange={this._changeShipDiscount} onFocus={selectAll} onBlur={this._setShipDiscount} onKeyDown={this._kpShipDiscount}/>
<u className='primary-disabled'>%</u>
</td>
</tr>
<tr>
<td>{translate('module')} {translate('discount')}</td>
<td className='ri'>
<input type='text' size='10' value={this.state.moduleDiscount} onChange={this._changeModuleDiscount} onFocus={selectAll} onBlur={this._setModuleDiscount} onKeyDown={this._kpModuleDiscount}/>
<u className='primary-disabled'>%</u>
</td>
</tr>
</tbody>
</table>
<hr /> <hr />
<ul> <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._showBackup.bind(this)}>{translate('backup')}</Link></li>
<li><Link href="#" className='block' onClick={this._showDetailedExport.bind(this)}>{translate('detailed export')}</Link></li> <li><Link href="#" className='block' onClick={this._showDetailedExport.bind(this)}>{translate('detailed export')}</Link></li>
<li><Link href="#" className='block' onClick={this._showImport.bind(this)}>{translate('import')}</Link></li> <li><Link href="#" className='block' onClick={this._showImport.bind(this)}>{translate('import')}</Link></li>
<li><Link href="#" onClick={this._showDeleteAll.bind(this)}>{translate('delete all')}</Link></li> <li><Link href="#" className='block' onClick={this._showDeleteAll.bind(this)}>{translate('delete all')}</Link></li>
</ul> </ul>
<hr /> <hr />
<table style={{ width: 300, backgroundColor: 'transparent' }}> <table style={{ width: 300, backgroundColor: 'transparent' }}>
@@ -462,32 +476,28 @@ export default class Header extends TranslatedComponent {
let translate = this.context.language.translate; let translate = this.context.language.translate;
let openedMenu = this.props.currentMenu; let openedMenu = this.props.currentMenu;
let hasBuilds = Persist.hasBuilds(); let hasBuilds = Persist.hasBuilds();
if (this.props.appCacheUpdate) {
return <div id="app-update" onClick={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>;
}
return ( return (
<header> <header>
{this.props.appCacheUpdate && <div id="app-update" onClick={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>}
<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'>
<div className={cn('menu-header', { selected: openedMenu == 's' })} onClick={this._openShips}> <div className={cn('menu-header', { selected: openedMenu == 's' })} onClick={this._openShips}>
<Rocket className='warning' /><span className='menu-item-label'>{' ' + translate('ships')}</span> <Rocket className='warning' /><span className='menu-item-label'>{translate('ships')}</span>
</div> </div>
{openedMenu == 's' ? this._getShipsMenu() : null} {openedMenu == 's' ? this._getShipsMenu() : null}
</div> </div>
<div className='l menu'> <div className='l menu'>
<div className={cn('menu-header', { selected: openedMenu == 'b', disabled: !hasBuilds })} onClick={hasBuilds && this._openBuilds}> <div className={cn('menu-header', { selected: openedMenu == 'b', disabled: !hasBuilds })} onClick={hasBuilds && this._openBuilds}>
<Hammer className={cn('warning', { 'warning-disabled': !hasBuilds })} /><span className='menu-item-label'>{' ' + translate('builds')}</span> <Hammer className={cn('warning', { 'warning-disabled': !hasBuilds })} /><span className='menu-item-label'>{translate('builds')}</span>
</div> </div>
{openedMenu == 'b' ? this._getBuildsMenu() : null} {openedMenu == 'b' ? this._getBuildsMenu() : null}
</div> </div>
<div className='l menu'> <div className='l menu'>
<div className={cn('menu-header', { selected: openedMenu == 'comp', disabled: !hasBuilds })} onClick={hasBuilds && this._openComp}> <div className={cn('menu-header', { selected: openedMenu == 'comp', disabled: !hasBuilds })} onClick={hasBuilds && this._openComp}>
<StatsBars className={cn('warning', { 'warning-disabled': !hasBuilds })} /><span className='menu-item-label'>{' ' + translate('compare')}</span> <StatsBars className={cn('warning', { 'warning-disabled': !hasBuilds })} /><span className='menu-item-label'>{translate('compare')}</span>
</div> </div>
{openedMenu == 'comp' ? this._getComparisonsMenu() : null} {openedMenu == 'comp' ? this._getComparisonsMenu() : null}
</div> </div>

View File

@@ -22,14 +22,14 @@ export default class InternalSlot extends Slot {
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}> return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}> <div className={'cb'}>
<div className={'l'}>{classRating + ' ' + translate(m.name || m.grp)}</div> <div className={'l'}>{classRating} {translate(m.name || m.grp)}</div>
<div className={'r'}>{m.mass || m.cargo || m.fuel || 0}{u.T}</div> <div className={'r'}>{m.mass || m.cargo || m.fuel || 0}{u.T}</div>
</div> </div>
<div className={'cb'}> <div className={'cb'}>
{ m.optmass ? <div className={'l'}>{translate('optimal mass') + ': '}{m.optmass}{u.T}</div> : null } { 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.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.bins ? <div className={'l'}>{m.bins} <u>{translate('bins')}</u></div> : null }
{ m.bays ? <div className={'l'}>{translate('bays') + ': ' + m.bays}</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.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.ammo ? <div className={'l'}>{translate('ammo')}: {formats.gen(m.ammo)}</div> : null }
{ m.cells ? <div className={'l'}>{translate('cells')}: {m.cells}</div> : null } { m.cells ? <div className={'l'}>{translate('cells')}: {m.cells}</div> : null }
@@ -38,10 +38,10 @@ export default class InternalSlot extends Slot {
{ m.range ? <div className={'l'}>{translate('range')} {m.range}{u.km}</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.time ? <div className={'l'}>{translate('time')}: {formats.time(m.time)}</div> : null }
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null } { m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
{ m.rangeLS ? <div className={'l'}>{m.rangeLS}{u.Ls}</div> : null } { m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
{ m.rangeLS === null ? <div className={'l'}><Infinite/>{u.Ls}</div> : null } { m.rangeLS === null ? <div className={'l'}>{u.Ls}</div> : null }
{ m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</div> : null } { m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</div> : null }
{ m.armouradd ? <div className={'l'}>+{m.armouradd} <u>{translate('armour')}</u></div> : null } { m.armouradd ? <div className={'l'}>+{m.armouradd} <u className='cap'>{translate('armour')}</u></div> : null }
</div> </div>
</div>; </div>;
} else { } else {

View File

@@ -58,7 +58,7 @@ export default class InternalSlotSection extends SlotSection {
let ship = this.props.ship; let ship = this.props.ship;
let chargeCap = 0; // Capacity of single activation let chargeCap = 0; // Capacity of single activation
ship.internal.forEach(function(slot) { 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 if ((!slot.m || (clobber && !ModuleUtils.isShieldGenerator(slot.m.grp))) && (!slot.eligible || slot.eligible.scb)) { // Check eligibility due to passenger ships special case
ship.use(slot, ModuleUtils.findInternal('scb', slot.maxClass, 'A')); ship.use(slot, ModuleUtils.findInternal('scb', slot.maxClass, 'A'));
ship.setSlotEnabled(slot, chargeCap <= ship.shieldStrength); // Don't waste cell capacity on overcharge ship.setSlotEnabled(slot, chargeCap <= ship.shieldStrength); // Don't waste cell capacity on overcharge
chargeCap += slot.m.recharge; chargeCap += slot.m.recharge;
@@ -76,7 +76,7 @@ export default class InternalSlotSection extends SlotSection {
let clobber = event.getModifierState('Alt'); let clobber = event.getModifierState('Alt');
let ship = this.props.ship; let ship = this.props.ship;
ship.internal.forEach((slot) => { ship.internal.forEach((slot) => {
if (clobber || !slot.c) { if (clobber || !slot.m) {
ship.use(slot, ModuleUtils.findInternal('hr', Math.min(slot.maxClass, 5), 'D')); // Hull reinforcements top out at 5D ship.use(slot, ModuleUtils.findInternal('hr', Math.min(slot.maxClass, 5), 'D')); // Hull reinforcements top out at 5D
} }
}); });
@@ -108,7 +108,7 @@ export default class InternalSlotSection extends SlotSection {
slots.push(<InternalSlot slots.push(<InternalSlot
key={i} key={i}
maxClass={s.maxClass} maxClass={s.maxClass}
availableModules={() => availableModules.getInts(s.maxClass, s.eligible)} availableModules={() => availableModules.getInts(ship, s.maxClass, s.eligible)}
onOpen={this._openMenu.bind(this,s)} onOpen={this._openMenu.bind(this,s)}
onSelect={this._selectModule.bind(this, s)} onSelect={this._selectModule.bind(this, s)}
selected={currentMenu == s} selected={currentMenu == s}
@@ -138,6 +138,7 @@ export default class InternalSlotSection extends SlotSection {
<li className='lc' onClick={this._fillWithCargo}>{translate('cargo')}</li> <li className='lc' onClick={this._fillWithCargo}>{translate('cargo')}</li>
<li className='lc' onClick={this._fillWithCells}>{translate('scb')}</li> <li className='lc' onClick={this._fillWithCells}>{translate('scb')}</li>
<li className='lc' onClick={this._fillWithArmor}>{translate('hr')}</li> <li className='lc' onClick={this._fillWithArmor}>{translate('hr')}</li>
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul> </ul>
</div>; </div>;
} }

View File

@@ -235,13 +235,13 @@ export default class LineChart extends TranslatedComponent {
<g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}> <g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}>
<text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}> <text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}>
<tspan>{xLabel}</tspan> <tspan>{xLabel}</tspan>
<tspan className='metric'>{` (${xUnit})`}</tspan> <tspan className='metric'> ({xUnit})</tspan>
</text> </text>
</g> </g>
<g className='y axis' ref={(elem) => d3.select(elem).call(this.yAxis)}> <g className='y axis' ref={(elem) => d3.select(elem).call(this.yAxis)}>
<text className='cap' transform='rotate(-90)' y='-50' dy='.1em' x={innerHeight / -2} style={{ textAnchor: 'middle' }}> <text className='cap' transform='rotate(-90)' y='-50' dy='.1em' x={innerHeight / -2} style={{ textAnchor: 'middle' }}>
<tspan>{yLabel}</tspan> <tspan>{yLabel}</tspan>
<tspan className='metric'>{` (${yUnit})`}</tspan> <tspan className='metric'> ({yUnit})</tspan>
</text> </text>
</g> </g>
<g ref={(g) => this.tipContainer = d3.select(g)} style={{ display: 'none' }}> <g ref={(g) => this.tipContainer = d3.select(g)} style={{ display: 'none' }}>

View File

@@ -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();

View File

@@ -271,8 +271,8 @@ export default class PowerBands extends TranslatedComponent {
<line x1={pctScale(0.5)} x2={pctScale(0.5)} y1='0' y2={state.innerHeight} className={pwrWarningClass} /> <line x1={pctScale(0.5)} x2={pctScale(0.5)} 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, available)}>{f2(Math.max(0, retSum))} ({pct1(Math.max(0, retSum / available))})</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, available)}>{f2(Math.max(0, depSum))} ({pct1(Math.max(0, depSum / available))})</text>
</g> </g>
</svg> </svg>
); );

View File

@@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import { wrapCtxMenu } from '../utils/UtilityFunctions'; import { wrapCtxMenu } from '../utils/UtilityFunctions';
import { canMount } from '../utils/SlotFunctions';
import { Equalizer } from '../components/SvgIcons'; import { Equalizer } from '../components/SvgIcons';
import cn from 'classnames'; import cn from 'classnames';
@@ -90,7 +91,7 @@ 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'; e.dataTransfer.dropEffect = os != targetSlot && canMount(this.props.ship, targetSlot, os.m.grp, os.m.class) ? 'copyMove' : 'none';
this.setState({ targetSlot }); this.setState({ targetSlot });
} else { } else {
e.dataTransfer.dropEffect = 'none'; e.dataTransfer.dropEffect = 'none';
@@ -116,9 +117,9 @@ export default class SlotSection extends TranslatedComponent {
let { originSlot, targetSlot } = this.state; let { originSlot, targetSlot } = this.state;
let m = originSlot.m; let m = originSlot.m;
if (targetSlot && m && targetSlot.maxClass >= m.class) { if (targetSlot && m && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
// Swap modules if possible // Swap modules if possible
if (targetSlot.m && originSlot.maxClass >= targetSlot.m.class) { 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(originSlot, targetSlot.m, true);
} else { // Otherwise empty the origin slot } else { // Otherwise empty the origin slot
this.props.ship.use(originSlot, null, true); // Empty but prevent summary update this.props.ship.use(originSlot, null, true); // Empty but prevent summary update
@@ -141,12 +142,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
} }

View File

@@ -33,6 +33,7 @@ export default class StandardSlot extends TranslatedComponent {
if (this.props.selected) { if (this.props.selected) {
menu = <AvailableModulesMenu menu = <AvailableModulesMenu
className='standard'
modules={modules} modules={modules}
shipMass={ship.ladenMass} shipMass={ship.ladenMass}
m={m} m={m}
@@ -47,16 +48,17 @@ export default class StandardSlot extends TranslatedComponent {
<div className={cn('details-container', { warning: warning && warning(slot.m) })}> <div className={cn('details-container', { warning: warning && warning(slot.m) })}>
<div className={'sz'}>{slot.maxClass}</div> <div className={'sz'}>{slot.maxClass}</div>
<div> <div>
<div className='l'>{classRating + ' ' + translate(m.grp)}</div> <div className='l'>{classRating} {translate(m.grp == 'bh' ? m.grp : m.name || m.grp)}</div>
<div className={'r'}>{m.mass || m.fuel}{units.T}</div> <div className={'r'}>{m.mass || m.fuel || 0}{units.T}</div>
<div className={'cb'}> <div className={'cb'}>
{ m.optmass ? <div className='l'>{translate('optimal mass') + ': '}{m.optmass}{units.T}</div> : null } { m.grp == 'bh' && m.name ? <div className='l'>{translate(m.name)}</div> : null }
{ m.maxmass ? <div className='l'>{translate('max mass') + ': '}{m.maxmass}{units.T}</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.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.time ? <div className='l'>{translate('time')}: {formats.time(m.time)}</div> : null }
{ m.eff ? <div className='l'>{translate('efficiency')}: {m.eff}</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.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.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.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.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 } { m.enginecapacity ? <div className='l'>{translate('ENG')}: {m.enginecapacity}{units.MJ} / {m.enginerecharge}{units.MW}</div> : null }

View File

@@ -4,6 +4,7 @@ import SlotSection from './SlotSection';
import StandardSlot from './StandardSlot'; import StandardSlot from './StandardSlot';
import { diffDetails } from '../utils/SlotFunctions'; import { diffDetails } from '../utils/SlotFunctions';
import * as ModuleUtils from '../shipyard/ModuleUtils'; import * as ModuleUtils from '../shipyard/ModuleUtils';
import * as ShipRoles from '../shipyard/ShipRoles';
import { stopCtxPropagation } from '../utils/UtilityFunctions'; import { stopCtxPropagation } from '../utils/UtilityFunctions';
/** /**
@@ -18,21 +19,8 @@ export default class StandardSlotSection extends SlotSection {
*/ */
constructor(props, context) { constructor(props, context) {
super(props, context, 'standard', 'standard'); super(props, context, 'standard', 'standard');
this._optimizeStandard = this._optimizeStandard.bind(this); this._optimizeStandard = this._optimizeStandard.bind(this);
this._optimizeCargo = this._optimizeCargo.bind(this); this._selectBulkhead = this._selectBulkhead.bind(this);
this._optimizeExplorer = this._optimizeExplorer.bind(this);
this._hideDiff = this._hideDiff.bind(this);
}
/**
* Fill all standard slots with the specificed rating (using max class)
* @param {String} rating [A-E]
*/
_fill(rating) {
this.props.ship.useStandard(rating);
this.props.onChange();
this._close();
} }
/** /**
@@ -45,85 +33,42 @@ export default class StandardSlotSection extends SlotSection {
} }
/** /**
* Trader build * Fill all standard slots with the specificed rating (using max class)
* @param {Boolean} shielded True if shield generator should be included
* @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames
*/ */
_optimizeCargo() { _multiPurpose(shielded, bulkheadIndex) {
let ship = this.props.ship; ShipRoles.multiPurpose(this.props.ship, shielded, bulkheadIndex);
ship.internal.forEach((slot) => ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E')));
ship.useLightestStandard();
this.props.onChange(); this.props.onChange();
this._close(); this._close();
} }
/** /**
* Explorer build * Trader Build
* @param {Boolean} shielded True if shield generator should be included
*/ */
_optimizeExplorer() { _optimizeCargo(shielded) {
let ship = this.props.ship, ShipRoles.trader(this.props.ship, shielded);
intLength = ship.internal.length, this.props.onChange();
heatSinkCount = 2, // Fit 2 heat sinks if possible this._close();
afmUnitCount = 2, // Fit 2 AFM Units if possible }
sgSlot,
fuelScoopSlot,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
ship.setSlotEnabled(ship.cargoHatch, false) /**
.use(ship.internal[--intLength], ModuleUtils.internal('2f')) // Advanced Discovery Scanner * Explorer role
.use(ship.internal[--intLength], ModuleUtils.internal('2i')); // Detailed Surface Scanner * @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included
*/
for (let i = 0; i < intLength; i++) { _optimizeExplorer(planetary) {
let slot = ship.internal[i]; ShipRoles.explorer(this.props.ship, planetary);
let nextSlot = (i + 1) < intLength ? ship.internal[i + 1] : null;
if (!fuelScoopSlot && (!slot.eligible || slot.eligible.fs)) { // Fit best possible Fuel Scoop
fuelScoopSlot = slot;
ship.use(fuelScoopSlot, ModuleUtils.findInternal('fs', slot.maxClass, 'A'));
ship.setSlotEnabled(fuelScoopSlot, true);
// Mount a Shield generator if possible AND an AFM Unit has been mounted already (Guarantees at least 1 AFM Unit)
} else if (!sgSlot && afmUnitCount < 2 && sg.class <= slot.maxClass && (!slot.eligible || slot.eligible.sg) && (!nextSlot || nextSlot.maxClass < sg.class)) {
sgSlot = slot;
ship.use(sgSlot, sg);
ship.setSlotEnabled(sgSlot, true);
} else if (afmUnitCount > 0 && (!slot.eligible || slot.eligible.am)) {
afmUnitCount--;
let am = ModuleUtils.findInternal('am', slot.maxClass, afmUnitCount ? 'B' : 'A');
ship.use(slot, am);
ship.setSlotEnabled(slot, false); // Disabled power for AFM Unit
} else {
ship.use(slot, null);
}
}
ship.hardpoints.forEach((s) => {
if (s.maxClass == 0 && heatSinkCount) { // Mount up to 2 heatsinks
ship.use(s, ModuleUtils.hardpoints('02'));
ship.setSlotEnabled(s, heatSinkCount == 2); // Only enable a single Heatsink
heatSinkCount--;
} else {
ship.use(s, null);
}
});
if (sgSlot) {
// The SG and Fuel scoop to not need to be powered at the same time
if (sgSlot.m.power > fuelScoopSlot.m.power) { // The Shield generator uses the most power
ship.setSlotEnabled(fuelScoopSlot, false);
} else { // The Fuel scoop uses the most power
ship.setSlotEnabled(sgSlot, false);
}
}
ship.useLightestStandard({ pd: '1D', ppRating: 'A' });
this.props.onChange(); this.props.onChange();
this._close(); this._close();
} }
/** /**
* Use the specified bulkhead * Use the specified bulkhead
* @param {number} bulkheadIndex 0 - 4 * @param {Object} bulkhead Bulkhead module details
*/ */
_selectBulkhead(bulkheadIndex) { _selectBulkhead(bulkhead) {
this.props.ship.useBulkhead(bulkheadIndex); this.props.ship.useBulkhead(bulkhead.index);
this.context.tooltip(); this.context.tooltip();
this.props.onChange(); this.props.onChange();
this._close(); this._close();
@@ -136,32 +81,12 @@ export default class StandardSlotSection extends SlotSection {
this._optimizeStandard(); this._optimizeStandard();
} }
/**
* Show the bulkhead diff tooltip
* @param {number} bhIndex Potential Bulkhead alternative
* @param {SyntheticEvent} event Event
*/
_bhDiff(bhIndex, event) {
let ship = this.props.ship;
this.context.tooltip(
diffDetails.call(ship, this.context.language, ModuleUtils.bulkheads(ship.id, bhIndex), ship.bulkheads.m),
event.currentTarget.getBoundingClientRect()
);
}
/**
* Hide the diff tooltip
*/
_hideDiff() {
this.context.tooltip();
}
/** /**
* Generate the slot React Components * Generate the slot React Components
* @return {Array} Array of Slots * @return {Array} Array of Slots
*/ */
_getSlots() { _getSlots() {
let { translate, units } = this.context.language; let { translate, units, formats } = this.context.language;
let { ship, currentMenu } = this.props; let { ship, currentMenu } = this.props;
let slots = new Array(8); let slots = new Array(8);
let open = this._openMenu; let open = this._openMenu;
@@ -171,41 +96,15 @@ export default class StandardSlotSection extends SlotSection {
let avail = ship.getAvailableModules().standard; let avail = ship.getAvailableModules().standard;
let bh = ship.bulkheads; let bh = ship.bulkheads;
slots[0] = ( slots[0] = <StandardSlot
<div key='bh' className={cn('slot', { selected: currentMenu === bh })} onClick={open.bind(this, bh)}> key='bh'
<div className={'details-container'}> slot={bh}
<div className={'details'}> modules={ship.getAvailableModules().bulkheads}
<div className={'sz'}>8</div> onOpen={open.bind(this, bh)}
<div> onSelect={this._selectBulkhead}
<div className={'l'}>{translate('bh')}</div> selected={currentMenu == bh}
<div className={'r'}>{bh.m.mass}{units.T}</div> ship={ship}
<div className={'cl l'}>{translate(bh.m.name)}</div> />;
</div>
</div>
</div>
{currentMenu === bh &&
<div className='select' onClick={ e => e.stopPropagation() }>
<ul>
<li onClick={selBulkhead.bind(this, 0)} onMouseOver={this._bhDiff.bind(this, 0)} onMouseLeave={this._hideDiff} className={cn('lc', { active: bh.index == 0 })}>
{translate('Lightweight Alloy')}
</li>
<li onClick={selBulkhead.bind(this, 1)} onMouseOver={this._bhDiff.bind(this, 1)} onMouseLeave={this._hideDiff} className={cn('lc', { active: bh.index == 1 })}>
{translate('Reinforced Alloy')}
</li>
<li onClick={selBulkhead.bind(this, 2)} onMouseOver={this._bhDiff.bind(this, 2)} onMouseLeave={this._hideDiff} className={cn('lc', { active: bh.index == 2 })}>
{translate('Military Grade Composite')}
</li>
<li onClick={selBulkhead.bind(this, 3)} onMouseOver={this._bhDiff.bind(this, 3)} onMouseLeave={this._hideDiff} className={cn('lc', { active: bh.index == 3 })}>
{translate('Mirrored Surface Composite')}
</li>
<li onClick={selBulkhead.bind(this, 4)} onMouseOver={this._bhDiff.bind(this, 4)} onMouseLeave={this._hideDiff} className={cn('lc', { active: bh.index == 4 })}>
{translate('Reactive Surface Composite')}
</li>
</ul>
</div>
}
</div>
);
slots[1] = <StandardSlot slots[1] = <StandardSlot
key='pp' key='pp'
@@ -226,7 +125,7 @@ export default class StandardSlotSection extends SlotSection {
onSelect={select.bind(this, st[1])} onSelect={select.bind(this, st[1])}
selected={currentMenu == st[1]} selected={currentMenu == st[1]}
ship={ship} ship={ship}
warning={m => m.maxmass < ship.ladenMass} warning={m => m.maxmass < (ship.ladenMass - st[1].mass + m.mass)}
/>; />;
@@ -292,21 +191,19 @@ export default class StandardSlotSection extends SlotSection {
* @return {React.Component} Section menu * @return {React.Component} Section menu
*/ */
_getSectionMenu(translate) { _getSectionMenu(translate) {
let _fill = this._fill; let planetaryDisabled = this.props.ship.internal.length < 4;
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}> return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul> <ul>
<li className='lc' onClick={this._optimizeStandard}>{translate('optimize')}</li> <li className='lc' onClick={this._optimizeStandard}>{translate('Maximize Jump Range')}</li>
<li className='c' onClick={_fill.bind(this, 'E')}>E</li>
<li className='c' onClick={_fill.bind(this, 'D')}>D</li>
<li className='c' onClick={_fill.bind(this, 'C')}>C</li>
<li className='c' onClick={_fill.bind(this, 'B')}>B</li>
<li className='c' onClick={_fill.bind(this, 'A')}>A</li>
</ul> </ul>
<div className='select-group cap'>{translate('roles')}</div> <div className='select-group cap'>{translate('roles')}</div>
<ul> <ul>
<li className='lc' onClick={this._optimizeCargo}>{translate('Trader')}</li> <li className='lc' onClick={this._multiPurpose.bind(this, false, 0)}>{translate('Multi-purpose')}</li>
<li className='lc' onClick={this._optimizeExplorer}>{translate('Explorer')}</li> <li className='lc' onClick={this._multiPurpose.bind(this, true, 2)}>{translate('Combat')}</li>
<li className='lc' onClick={this._optimizeCargo.bind(this, false)}>{translate('Trader')}</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

@@ -64,8 +64,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>;
} }
} }
@@ -319,6 +319,90 @@ export class Warning extends SvgIcon {
} }
} }
/**
* 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 +418,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 +442,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,9 +463,14 @@ 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>;
} }
} }

View File

@@ -94,6 +94,7 @@ export default class UtilitySlotSection extends SlotSection {
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}> return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul> <ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li> <li className='lc' onClick={this._empty}>{translate('empty all')}</li>
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('sb')}</div> <div className='select-group cap'>{translate('sb')}</div>
<ul> <ul>

View File

@@ -41,29 +41,31 @@ export function getLanguage(langCode) {
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.numberFormat(',.0f'), // Fixed to 0 decimal places (.e.g 1,001)
f1: d3Locale.numberFormat(',.1f'), // Fixed to 1 decimal place (.e.g 1,001.1)
f2: d3Locale.numberFormat(',.2f'), // Fixed to 2 decimal places (.e.g 1,001.10) f2: d3Locale.numberFormat(',.2f'), // Fixed to 2 decimal places (.e.g 1,001.10)
s2: d3Locale.numberFormat('.2s'), // SI Format to 2 decimal places (.e.g 1.1k) s2: d3Locale.numberFormat('.2s'), // SI Format to 2 decimal places (.e.g 1.1k)
pct: d3Locale.numberFormat('.2%'), // % to 2 decimal places (.e.g 5.40%) pct: d3Locale.numberFormat('.2%'), // % to 2 decimal places (.e.g 5.40%)
pct1: d3Locale.numberFormat('.1%'), // % to 1 decimal places (.e.g 5.4%) pct1: d3Locale.numberFormat('.1%'), // % to 1 decimal places (.e.g 5.4%)
r2: d3Locale.numberFormat('.2r'), // Rounded to 2 significant numbers (.e.g 512 => 510, 4.122 => 4.1) r2: d3Locale.numberFormat('.2r'), // Rounded to 2 significant numbers (.e.g 512 => 510, 4.122 => 4.1)
rPct: d3.format('%'), // % to 0 decimal places (.e.g 5%) rPct: d3.format('%'), // % to 0 decimal places (.e.g 5%)
round1: (d) => gen(d3.round(d, 1)), // Rounded to 0-1 decimal places (.e.g 5.1, 4)
round: (d) => gen(d3.round(d, 2)), // Rounded to 0-2 decimal places (.e.g 5.12, 4.1) round: (d) => gen(d3.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 CR: <u> {translate('CR')}</u>, // Credits
kg: <u>{' ' + translate('kg')}</u>, // Kilograms kg: <u> {translate('kg')}</u>, // Kilograms
kgs: <u>{' ' + translate('kg/s')}</u>, // Kilograms per second kgs: <u> {translate('kg/s')}</u>, // Kilograms per second
km: <u>{' ' + translate('km')}</u>, // Kilometers km: <u> {translate('km')}</u>, // Kilometers
Ls: <u>{' ' + translate('Ls')}</u>, // Light Seconds Ls: <u> {translate('Ls')}</u>, // Light Seconds
LY: <u>{' ' + translate('LY')}</u>, // Light Years LY: <u> {translate('LY')}</u>, // Light Years
MJ: <u>{' ' + translate('MJ')}</u>, // Mega Joules MJ: <u> {translate('MJ')}</u>, // Mega Joules
'm/s': <u>{' ' + translate('m/s')}</u>, // Meters per second 'm/s': <u> {translate('m/s')}</u>, // Meters per second
MW: <u>{' ' + translate('MW')}</u>, // Mega Watts (same as Mega Joules per second) MW: <u> {translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
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 T: <u> {translate('T')}</u>, // Metric Tons
} }
}; };
} }

View File

@@ -15,76 +15,20 @@ export const formats = {
export const terms = { export const terms = {
// Phrases // Phrases
PHRASE_BACKUP_DESC: 'Export aller Coriolis-Daten, um sie zu sichern oder 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_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_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_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: null, // Consecutive max range jumps 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_IMPORT: 'JSON hier einfügen oder importieren', // Paste JSON or import here
PHRASE_LADEN: null, // Ship Mass + Fuel + Cargo 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_BUILDS: 'Keine Konfigurationen zum Vergleich ausgewählt!', // No builds added to comparison!
PHRASE_NO_RETROCH: 'Keine Umrüständerungen', // No Retrofitting changes PHRASE_NO_RETROCH: 'Keine Umrüständerungen', // No Retrofitting changes
PHRASE_SELECT_BUILDS: 'Ausstattung zum Vergleich auswählen', // Select Builds to Compare PHRASE_SELECT_BUILDS: 'Ausstattung zum Vergleich auswählen', // Select Builds to Compare
PHRASE_SG_RECHARGE: null, // Time from 50% to 100% Charge PHRASE_SG_RECHARGE: 'Zeit von 50% bis 100% der Ladung', // Time from 50% to 100% Charge
PHRASE_SG_RECOVER: null, // Recovery (to 50%) after collapse PHRASE_SG_RECOVER: 'Erneuerung (zu 50%) nach Zusammenbruch', // Recovery (to 50%) after collapse
PHRASE_UNLADEN: null, // Ship Mass excluding Fuel and Cargo 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 PHRASE_UPDATE_RDY: 'Update verfügbar! Klicken zum Aktualisieren', // Update Available! Click to Refresh
// Modules / Module Types - These should match the in-game translation (if any)
'Basic Discovery Scanner': 'einfacher Aufklärungsscanner', // Basic Discovery Scanner
'Cargo Hatch': 'Frachtluke', // Cargo Hatch
'Chaff Launcher': 'Düppel-Werfer', // Chaff Launcher
'Detailed Surface Scanner': 'Detailoberflächenscanner', // Detailed Surface Scanner
'Electronic Countermeasure': 'elektronische Gegenmaßnahme', // Electronic Countermeasure
'Heat Sink Launcher': 'Kühlkörperwerfer', // Heat Sink Launcher
'Intermediate Discovery Scanner': 'mittlerer Aufklärungsscanner', // Intermediate Discovery Scanner
'Point Defence': 'Punktverteidigung',
'Standard Docking Computer': 'Standard-Landecomputer', // Standard Docking Computer
am: 'automatische Feldwartungs-Einheit', // Auto Field-Maintenance Unit
bh: 'Rumpfhüllenverstärkung', // Bulkheads
bl: 'Strahlenlaser', // Beam Laser
c: 'Kanone', // Cannon
cc: 'Krallensteuerung: Sammler', // Collector Limpet Controller
cm: 'Gegenmaßnahme', // Countermeasure
cr: 'Frachtgestell', // Cargo Rack
cs: 'Frachtscanner', // Cargo Scanner
dc: 'Standard-Landecomputer', // Docking Computer
fc: 'Splitterkanone', // Fragment Cannon
fi: 'FSA-Unterbrecher', // FSD Interdictor
fs: 'Treibstoffsammler', // Fuel Scoop
fsd: 'Frameshiftantrieb', // Frame Shift Drive
ft: 'Treibstofftank', // Fuel Tank
fx: 'Krallensteuerung Treibstoffstransfer', // Fuel Transfer Limpet Controller
hb: 'Krallen-Steuereinheit (Ladelukenöffner)', // Hatch Breaker Limpet Controller
hr: 'Rumpfhüllenverstärkung (Paket)', // Hull Reinforcement Package
kw: 'Tötungsbefehl-Scanner', // Kill Warrant Scanner
ls: 'Lebenserhaltung', // life support
mc: 'Mehrfachgeschütz', // Multi-cannon
ml: 'Abbaulaser', // Mining Laser
mr: 'Raketenbatterie', // Missile Rack
nl: 'Minenwerfer', // Mine Launcher
pa: 'Plasmabeschleuniger', // Plasma Accelerator
pc: 'Krallensteuerung: Erzsucher', // Prospector Limpet Controller
pd: 'Energieverteiler', // power distributor
pl: 'Impulslaser', // Pulse Laser
pp: 'Kraftwerk', // Power Plant
psg: 'Prismaschildgenerator', // Prismatic Shield Generator
rf: 'Raffinerie', // Refinery
rg: 'Schienenkanone', // Rail Gun
s: 'Sensoren', // Sensors
sb: 'Schild-Booster', // Shield Booster
sc: 'Scanner', // Scanner
scb: 'Schildzellenbank', // Shield Cell Bank
sg: 'Schildgenerator', // Shield Generator
t: 'Schubdüsen', // Thrusters
tp: 'Torpedoaufhängung', // Torpedo Pylon
ul: 'Salvenlaser', // Burst Laser
ws: 'Frameshift-Sogwolkenscanner', // Frame Shift Wake Scanner
// Bulkheads - These should match the in-game translation (if any)
'Lightweight Alloy': 'leichte Legierung',
'Reinforced Alloy': 'verstärkte Legierung',
'Military Grade Composite': 'Militär-Komposit',
'Mirrored Surface Composite': 'Gespiegelte-Oberfläche-Komposit',
'Reactive Surface Composite': 'Reaktive-Oberfläche-Komposit',
// Units / Metrics // Units / Metrics
LY: 'Lj', // Light Years LY: 'Lj', // Light Years
T: 't', // Tons (Metric Ton - 1000kg) T: 't', // Tons (Metric Ton - 1000kg)
@@ -94,109 +38,108 @@ export const terms = {
L: 'G', // Large Hardpoint size (single character) L: 'G', // Large Hardpoint size (single character)
H: 'R', // Huge Hardpoint size (single character) H: 'R', // Huge Hardpoint size (single character)
U: 'W', // Utility Hardpoint size (single character) - Kill warrant scanner, etc 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 // Terms
'build name': 'Ausstattungsname', // Ship build/configuration/design name about: 'über', // Link to about page / about Coriolis.io
'compare all': 'Alles vergleichen',
'create new': 'Neu erstellen',
'damage per second': null,
'delete all': 'Alles Löschen',
'detailed export': 'detailierter Export',
'edit data': 'bearbeiten',
'empty all': 'leer alles',
'Enter Name': 'Namen eingeben',
'fastest range': null, // Fastet totaljump range - sum of succesive jumps
'fuel level': null, // Percent of fuel (T) in the tank
'full tank': 'Tank voll',
'internal compartments': 'Innenbereich',
'jump range': 'Sprungreichweite',
'mass lock factor': 'Massensperrefaktor',
'max mass': 'maximale Masse',
'net cost': 'Nettokosten',
'none created': 'Leer',
'optimal mass': null, // Lowest weight / best weight for jump distance, etc
'refuel time': 'Auftankzeit', // Time to refuel the tank when scooping
'reload costs': null,
'retrofit costs': 'Änderungskosten', // The cost difference when upgrading / downgrading a component
'retrofit from': 'Nachrüsten von', // Retrofit from Build A against build B
'T-Load': 'T-Lad', // Thermal load abbreviation
'total range': null,
'unit cost': null,
'utility mounts': 'Werkzeug-Steckplätze',
about: 'Über', // Link to about page / about Coriolis.io
action: 'Aktion', action: 'Aktion',
added: 'hinzugefügt', added: 'hinzugefügt',
ammo: 'Munition', // Ammunition ammo: 'Munition', // Ammunition
armour: 'Panzerung', armour: 'Panzerung',
available: 'verfügbar', available: 'verfügbar', // Available options
backup: 'Sicherungsdatei', backup: 'Sicherungsdatei',
base: null, base: 'Basis', // Base speed, boost, etc - Base ship stats
bays: null, bays: 'Lagerraum',
bins: 'Behälter', // Number of Mining Refinery bins bins: 'Behälter', // Number of Mining Refinery bins
build: 'Ausstattung', // Shorthand for the build/configuration/design name build: 'Ausstattung', // Shorthand for the build/configuration/design name
'build name': 'Ausstattungsname', // Ship build/configuration/design name
builds: 'Ausstattungen', // Ship build/configuration/design names builds: 'Ausstattungen', // Ship build/configuration/design names
buy: 'kaufen', buy: 'kaufen',
cancel: 'Abbrechen', cancel: 'abbrechen',
cargo: 'Fracht', cargo: 'Fracht',
cells: 'Zellen', // Number of cells in a shield cell bank cells: 'Zellen', // Number of cells in a shield cell bank
close: 'Schließen', close: 'schließen',
compare: 'vergleichen', compare: 'vergleichen',
'compare all': 'alles vergleichen',
comparison: 'Vergleich', comparison: 'Vergleich',
comparisons: 'Vergleiche', comparisons: 'Vergleiche',
cost: 'Preis', // Cost / price of a module or price of a ship cost: 'Preis', // Cost / price of a module or price of a ship
costs: 'Kosten', // Costs / prices of a modules or prices of ships costs: 'Kosten', // Costs / prices of a modules or prices of ships
create: 'erstellen', create: 'erstellen',
'create new': 'neu erstellen',
credits: 'Credits', credits: 'Credits',
damage: 'Schaden', damage: 'Schaden',
delete: 'Löschen', 'damage per second': 'Schaden pro Sekunde',
dep: 'Ausg', // Weapons/Hardpoints Deployed abbreviation delete: 'löschen',
deployed: 'Ausgefahren', // Weapons/Hardpoints Deployed 'delete all': 'alles löschen',
disabled: 'Deaktiviert', dep: 'ausg', // Weapons/Hardpoints Deployed abbreviation
deployed: 'ausgefahren', // Weapons/Hardpoints Deployed
'detailed export': 'detailierter Export',
disabled: 'deaktiviert',
discount: 'Rabatt', discount: 'Rabatt',
'edit data': 'bearbeiten',
efficiency: 'Effizienz', // Power Plant efficiency efficiency: 'Effizienz', // Power Plant efficiency
empty: 'leer', empty: 'leer',
'empty all': 'alles entfernen',
ENG: 'ANT', // Abbreviation - Engine recharge rate for power distributor ENG: 'ANT', // Abbreviation - Engine recharge rate for power distributor
'Enter Name': 'Namen eingeben',
Explorer: 'Forscher', Explorer: 'Forscher',
export: 'Export',
'fastest range': 'maximale Reichweite', // Fastet totaljump range - sum of succesive jumps
forum: 'Forum', forum: 'Forum',
fuel: 'Treibstoff', fuel: 'Treibstoff',
'fuel level': 'Tankfüllstand', // Percent of fuel (T) in the tank
'full tank': 'Tank voll',
hardpoints: 'Waffenaufhängungen', hardpoints: 'Waffenaufhängungen',
hull: 'Hülle', // Ships hull hull: 'Rumpf', // Ships hull
import: 'importieren', import: 'importieren',
insurance: 'Versicherung', insurance: 'Versicherung',
jump: null, // Single jump range 'internal compartments': 'Innenbereich',
jump: 'Sprung', // Single jump range
'jump range': 'Sprungreichweite',
jumps: 'Sprünge', jumps: 'Sprünge',
laden: 'beladen', laden: 'beladen',
language: 'Sprache', language: 'Sprache',
maneuverability: 'Manövrierbarkeit', maneuverability: 'Manövrierbarkeit',
manufacturer: null, manufacturer: 'Hersteller',
mass: 'Masse', mass: 'Masse',
MLF: null, // Mass Lock Factor Abbreviation 'mass lock factor': 'Massensperrefaktor',
MNV: null, // Maneuverability abbreviation 'max mass': 'maximale Masse',
module: 'modul', MLF: 'MSF', // Mass Lock Factor Abbreviation
modules: 'module', module: 'Modul',
modules: 'Module',
'net cost': 'Nettokosten',
no: 'Nein', no: 'Nein',
'none created': 'nicht erstellt',
ok: 'OK', ok: 'OK',
optimize: null, 'optimal mass': 'optimale Masse', // Lowest weight / best weight for jump distance, etc
optimize: 'optimieren',
pen: 'Durchdr.', // Armour peneration abbreviation 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 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 pri: 'Prio', // Priority abbreviation for power management
proceed: 'Fortfahren', proceed: 'fortfahren',
PWR: 'En', // Power Abbreviation. See Power qty: 'Menge', // Quantity abbreviation
qty: null, // Quantity abbreviation
range: 'Reichweite', range: 'Reichweite',
rate: 'Rate', rate: 'Rate',
recharge: null, // Shield Recharge time from 50% -> 100% recharge: 'aufladen', // Shield Recharge time from 50% -> 100%
recovery: null, // Shield recovery time (after losing shields/turning on -> 50%) 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: 'aktualisieren', // Reload weapon/hardpoint
'reload costs': 'Nachladekosten',
rename: 'umbenennen', rename: 'umbenennen',
repair: 'reparieren', repair: 'reparieren',
reset: 'zurücksetzen', reset: 'zurücksetzen',
ret: 'Eing', // Retracted abbreviation ret: 'eing', // Retracted abbreviation
retracted: 'Eingefahren', // Weapons/Hardpoints retracted 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 ROF: 'Kad', // Rate of Fire abbreviation
roles: null, // Commander/Ship build roles - e.g. Trader, Bounty-Hunter, Explorer, etc roles: 'Rollen', // Commander/Ship build roles - e.g. Trader, Bounty-Hunter, Explorer, etc
save: 'Speichern', save: 'speichern',
sell: 'Verkaufen', sell: 'verkaufen',
settings: 'Einstellungen', // Coriolis application settings settings: 'Einstellungen', // Coriolis application settings
shields: 'Schilde', shields: 'Schilde',
ship: 'Schiff', ship: 'Schiff',
@@ -205,16 +148,19 @@ export const terms = {
size: 'Größe', size: 'Größe',
skip: 'überspringen', // Skip past something / ignore it skip: 'überspringen', // Skip past something / ignore it
speed: 'Geschwindigkeit', speed: 'Geschwindigkeit',
standard: 'Standard', // Standard / Common modules (FSD, power plant, life support, etc) standard: 'Grundausstattung', // Standard / Common modules (FSD, power plant, life support, etc)
Stock: 'Standard', // Thermal-load abbreviation Stock: 'Standard', // Thermal-load abbreviation
strength: null, // Strength in reference to Shield Strength strength: 'Stärke', // Strength in reference to Shield Strength
subtotal: null, subtotal: 'Zwischensumme',
time: 'Dauer', // time it takes to complete something time: 'Dauer', // time it takes to complete something
tooltips: null, // Tooltips setting - show/hide tooltips: 'Tooltips', // Tooltips setting - show/hide
total: 'Gesamt', total: 'gesamt',
Trader: null, // Trader role 'total range': 'Gesamtbereich',
Trader: 'Händler', // Trader role
type: 'Typ', type: 'Typ',
unladen: 'Unbeladen', // No cargo or fuel '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 WEP: 'WAF', // Abbreviation - Weapon recharge rate for power distributor
yes: 'Ja' yes: 'ja'
}; };

View File

@@ -14,20 +14,22 @@ export const formats = {
}; };
export const terms = { export const terms = {
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_BACKUP_DESC: 'Backup of all Coriolis data to save or transfer to another browser/device',
PHRASE_CONFIRMATION: 'Are You Sure?', PHRASE_CONFIRMATION: 'Are you sure?',
PHRASE_EXPORT_DESC: 'A detailed JSON export of your build for use in other sites and tools', 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_FASTEST_RANGE: 'Consecutive max range jumps',
PHRASE_IMPORT: 'Paste JSON or import here', PHRASE_IMPORT: 'Paste JSON or import here',
PHRASE_LADEN: 'Ship Mass + Fuel + Cargo', PHRASE_LADEN: 'Ship mass + fuel + cargo',
PHRASE_NO_BUILDS: 'No builds added to comparison!', PHRASE_NO_BUILDS: 'No builds added to comparison!',
PHRASE_NO_RETROCH: 'No Retrofitting changes', PHRASE_NO_RETROCH: 'No Retrofitting changes',
PHRASE_SELECT_BUILDS: 'Select Builds to Compare', PHRASE_SELECT_BUILDS: 'Select builds to compare',
PHRASE_SG_RECHARGE: 'Time from 50% to 100% Charge', PHRASE_SG_RECHARGE: 'Time from 50% to 100% charge',
PHRASE_SG_RECOVER: 'Recovery (to 50%) after collapse', PHRASE_SG_RECOVER: 'Recovery (to 50%) after collapse',
PHRASE_UNLADEN: 'Ship Mass excluding Fuel and Cargo', PHRASE_UNLADEN: 'Ship mass excluding fuel and cargo',
PHRASE_UPDATE_RDY: 'Update Available! Click to Refresh', 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 // Only Translate to other languages if the name is different in-game
am: 'Auto Field-Maintenance Unit', am: 'Auto Field-Maintenance Unit',
bh: 'Bulkheads', bh: 'Bulkheads',
@@ -40,6 +42,7 @@ export const terms = {
cs: 'Cargo Scanner', cs: 'Cargo Scanner',
dc: 'Docking Computer', dc: 'Docking Computer',
fc: 'Fragment Cannon', fc: 'Fragment Cannon',
fh: 'Fighter Hangar',
fi: 'FSD Interdictor', fi: 'FSD Interdictor',
fs: 'Fuel Scoop', fs: 'Fuel Scoop',
fsd: 'Frame Shift Drive', fsd: 'Frame Shift Drive',
@@ -56,11 +59,15 @@ export const terms = {
pa: 'Plasma Accelerator', pa: 'Plasma Accelerator',
pas: 'Planetary Approach Suite', pas: 'Planetary Approach Suite',
pc: 'Prospector Limpet Controller', pc: 'Prospector Limpet Controller',
pce: 'Economy Class Passenger Cabin',
pci: 'Business Class Passenger Cabin',
pcm: 'First Class Passenger Cabin',
pcq: 'Luxury Passenger Cabin',
pd: 'power distributor', pd: 'power distributor',
pl: 'Pulse Laser', pl: 'Pulse Laser',
pp: 'Power Plant', pp: 'Power Plant',
psg: 'Prismatic Shield Generator', psg: 'Prismatic Shield Generator',
pv: 'Planetary Vehicle Hanger', pv: 'Planetary Vehicle Hangar',
rf: 'Refinery', rf: 'Refinery',
rg: 'Rail Gun', rg: 'Rail Gun',
s: 'Sensors', s: 'Sensors',

View File

@@ -15,119 +15,42 @@ export const formats = {
export const terms = { export const terms = {
// Phrases // Phrases
PHRASE_BACKUP_DESC: 'Exportation détaillée 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_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_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_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_IMPORT: 'Coller JSON ou importer ici', // Paste JSON or import here
PHRASE_NO_BUILDS: 'Défaut de configuration pour comparaison', // No builds added to comparison! PHRASE_LADEN: 'Masse du Vaisseau + Carburant + Cargo', // Ship Mass + Fuel + Cargo
PHRASE_NO_RETROCH: 'configuration non modifiée', // No Retrofitting changes PHRASE_NO_BUILDS: 'Aucune configuration ajoutée pour la comparaison', // No builds added to comparison!
PHRASE_SELECT_BUILDS: 'Sélectionner configurations à comparer', // Select Builds to Compare 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 PHRASE_UPDATE_RDY: 'Mise à jour disponible ! Cliquez pour rafraichir', // Update Available! Click to Refresh
// Modules / Module Types - These should match the in-game translation (if any)
am: 'Unité de maintenance de terrain auto', // Auto Field-Maintenance Unit
'Basic Discovery Scanner': 'Détecteur de découverte simple', // Basic Discovery Scanner
bh: 'Coque', // Bulkheads
bl: 'Rayon Laser', // Beam Laser
'Cargo Hatch': 'Ecoutille de soute', // Cargo Hatch
c: 'Canon', // Cannon
cc: 'Contrôleur de collecteurs', // Collector Limpet Controller
cm: 'Contre-mesure', // Countermeasure
'Chaff Launcher': 'Lanceur de paillettes', // Chaff Launcher
cr: 'Compartiment de soute', // Cargo Rack
cs: 'Scanner de soute', // Cargo Scanner
dc: 'Ordinateur d\'appontage', // Docking Computer
'Detailed Surface Scanner': 'Détecteur de surface détaillé', // Detailed Surface Scanner
'Electronic Countermeasure': 'Contre mesure électronique', // Electronic Countermeasure
fc: 'Canon à fragmentation', // Fragment Cannon
fi: 'Intercepteur de réacteur FSD', // FSD Interdictor
fs: 'Récupérateur de carburant', // Fuel Scoop
fsd: 'Réacteur FSD', // Frame Shift Drive
ft: 'Réservoir de carburant', // Fuel Tank
fx: 'Drone de ravitaillement', // Fuel Transfer Limpet Controller
'Heat Sink Launcher': 'Ejecteur de dissipateur thermique', // Heat Sink Launcher
hb: 'Contrôle de patelle perce-soute', // Hatch Breaker Limpet Controller
hr: 'Renfort de soute', // Hull Reinforcement Package
'Intermediate Discovery Scanner': 'Détecteur de découverte intermédiaire', // Intermediate Discovery Scanner
kw: 'Détecteur d\'avis de recherche', // Kill Warrant Scanner
ls: 'Support vital', // life support
mc: 'Canon multiple', // Multi-cannon
ml: 'Laser minier', // Mining Laser
mr: 'Lance missiles', // Missile Rack
nl: 'Lance-mines', // Mine Launcher
pa: 'accélérateur plasma', // Plasma Accelerator
pc: 'Drône de minage', // Prospector Limpet Controller
pd: 'distributeur d\'énérgie', // power distributor
pl: 'Laser à impulsion', // Pulse Laser
'Point Defence': 'Défense ponctuelle',
pp: 'centrale d\'énergie', // Power Plant
psg: 'générateur de bouclier prisme', // Prismatic Shield Generator
rf: 'Raffinerie', // Refinery
rg: 'Canon électromagnétique', // Rail Gun
s: 'détecteurs', // Sensors
sb: 'Survolteur de bouclier', // Shield Booster
sc: 'scanner', // Scanner
scb: 'Réserve de cellules d\'énergie', // Shield Cell Bank
sg: 'Générateur de bouclier', // Shield Generator
'Standard Docking Computer': 'ordinateur d\'appontage standard', // Standard Docking Computer
t: 'propulseurs', // Thrusters
tp: 'Tube lance-torpille', // Torpedo Pylon
ul: 'Laser à rafale', // Burst Laser
ws: 'Détecteur de sillage FSD', // Frame Shift Wake Scanner
// Bulkheads - These should match the in-game translation (if any)
'Lightweight Alloy': 'alliage léger',
'Reinforced Alloy': 'alliage renforcé',
'Military Grade Composite': 'Composite militaire',
'Mirrored Surface Composite': 'Composite à surface mirroir',
'Reactive Surface Composite': 'Composite à surface réactive',
// Units / Metrics // Units / Metrics
Ls: 'SL', // Light seconds Ls: 'SL', // Light seconds
LY: 'AL', // Light Years LY: 'AL', // Light Years
// Sizes // Sizes
S: 'P', // Small Hardpoint (single Character)
L: 'G', // Large Hardpoint size (single character) 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 // Terms
'build name': 'Nom de la configuration', // Ship build/configuration/design name
'compare all': 'tout comparer',
'create new': 'Créer nouveau',
'damage per second': null,
'delete all': 'tout supprimer',
'detailed export': 'export détaillé',
'edit data': 'Editer donnée',
'empty all': null,
'Enter Name': 'Entrer nom',
'fastest range': null, // Fastet totaljump range - sum of succesive jumps
'fuel level': null, // Percent of fuel (T) in the tank
'full tank': 'Réservoir plein',
'internal compartments': 'compartiments internes',
'jump range': 'Distance de saut',
'mass lock factor': 'facteur inhibition de masse',
'max mass': 'masse max',
'net cost': 'coûts nets',
'none created': 'Rien de créé',
'optimal mass': null, // Lowest weight / best weight for jump distance, etc
'refuel time': 'Temps de remplissage', // Time to refuel the tank when scooping
'reload costs': null,
'retrofit costs': 'Valeur de rachat', // The cost difference when upgrading / downgrading a component
'retrofit from': 'Racheter de', // Retrofit from Build A against build B
'T-Load': 'degrés', // Thermal load abbreviation
'total range': null,
'unit cost': null,
'utility mounts': 'Support utilitaire',
about: 'à propos', // Link to about page / about Coriolis.io about: 'à propos', // Link to about page / about Coriolis.io
added: 'ajouté', added: 'ajouté',
ammo: 'munition', // Ammunition ammo: 'munition', // Ammunition
armour: 'Armure', armour: 'Armure',
available: 'Disponibilité', available: 'Disponibilité', // Available options
backup: 'sauvegarde', backup: 'sauvegarde',
base: null, bays: 'baies',
bays: null,
bins: 'bacs', // Number of Mining Refinery bins bins: 'bacs', // Number of Mining Refinery bins
build: 'Configuration', // Shorthand for the build/configuration/design name 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 builds: 'Configurations', // Ship build/configuration/design names
buy: 'Acheter', buy: 'Acheter',
cancel: 'Annuler', cancel: 'Annuler',
@@ -135,58 +58,77 @@ export const terms = {
cells: 'Cellule', // Number of cells in a shield cell bank cells: 'Cellule', // Number of cells in a shield cell bank
close: 'fermer', close: 'fermer',
compare: 'comparer', compare: 'comparer',
'compare all': 'tout comparer',
comparison: 'comparaison', comparison: 'comparaison',
comparisons: 'comparaisons', comparisons: 'comparaisons',
cost: 'coût', // Cost / price of a module or price of a ship 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 costs: 'coûts', // Costs / prices of a modules or prices of ships
create: 'Créer', create: 'Créer',
'create new': 'Créer nouveau',
credits: 'crédits', credits: 'crédits',
damage: 'Dégâts', damage: 'dégât',
'damage per second': 'dégât par seconde',
delete: 'supprimer', delete: 'supprimer',
'delete all': 'tout supprimer',
dep: 'depl', // Weapons/Hardpoints Deployed abbreviation dep: 'depl', // Weapons/Hardpoints Deployed abbreviation
deployed: 'déployé', // Weapons/Hardpoints Deployed deployed: 'déployé', // Weapons/Hardpoints Deployed
'detailed export': 'export détaillé',
disabled: 'désactivé', disabled: 'désactivé',
discount: 'ristourne', discount: 'ristourne',
'edit data': 'Editer donnée',
efficiency: 'rendement', // Power Plant efficiency efficiency: 'rendement', // Power Plant efficiency
empty: 'Vide', empty: 'Vide',
Explorer: null, '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: 'carburant',
'fuel level': 'niveau de carburant', // Percent of fuel (T) in the tank
'full tank': 'Réservoir plein',
hardpoints: 'Points d\'emport', hardpoints: 'Points d\'emport',
hull: 'Coque', // Ships hull hull: 'Coque', // Ships hull
import: 'Importer', import: 'Importer',
insurance: 'Assurance', insurance: 'Assurance',
jump: null, // Single jump range 'internal compartments': 'compartiments internes',
jump: 'saut', // Single jump range
'jump range': 'Distance de saut',
jumps: 'Sauts', jumps: 'Sauts',
laden: 'chargé', laden: 'chargé',
language: 'Langage', language: 'Langage',
maneuverability: null, maneuverability: 'maniabilité',
manufacturer: null, manufacturer: 'fabricant',
mass: 'Masse', mass: 'Masse',
MLF: null, // Mass Lock Factor Abbreviation 'mass lock factor': 'facteur inhibition de masse',
MNV: null, // Maneuverability abbreviation 'max mass': 'masse max',
module: null, MLF: 'FIM', // Mass Lock Factor Abbreviation
modules: null, 'net cost': 'coûts nets',
no: 'non', no: 'non',
'none created': 'Rien de créé',
ok: 'D\'accord', ok: 'D\'accord',
optimize: null, 'optimal mass': 'masse optimale', // Lowest weight / best weight for jump distance, etc
optimize: 'optimiser',
pen: 'pén.', // Armour peneration abbreviation pen: 'pén.', // Armour peneration abbreviation
permalink: 'lien durable', 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 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', proceed: 'continuer',
PWR: 'P', // Power Abbreviation. See Power PWR: 'P', // Power Abbreviation. See Power
qty: null, // Quantity abbreviation qty: 'quantité', // Quantity abbreviation
range: 'portée', range: 'portée',
rate: 'cadence', rate: 'cadence',
recharge: null, // Shield Recharge time from 50% -> 100% recharge: 'recharger', // Shield Recharge time from 50% -> 100%
recovery: null, // Shield recovery time (after losing shields/turning on -> 50%) 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: 'recharger', // Reload weapon/hardpoint
'reload costs': 'recharger coûts',
rename: 'renommer', rename: 'renommer',
repair: 'réparer', repair: 'réparer',
reset: 'Réinitialisation', reset: 'Réinitialisation',
ret: 'esc', // Retracted abbreviation ret: 'esc', // Retracted abbreviation
retracted: 'escamoté', // Weapons/Hardpoints retracted 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 ROF: 'cadence', // Rate of Fire abbreviation
roles: null, // Commander/Ship build roles - e.g. Trader, Bounty-Hunter, Explorer, etc roles: 'rôles', // Commander/Ship build roles - e.g. Trader, Bounty-Hunter, Explorer, etc
save: 'sauvegarder', save: 'sauvegarder',
sell: 'vendre', sell: 'vendre',
settings: 'paramètres', // Coriolis application settings settings: 'paramètres', // Coriolis application settings
@@ -198,12 +140,16 @@ export const terms = {
skip: 'Suivant', // Skip past something / ignore it skip: 'Suivant', // Skip past something / ignore it
speed: 'vitesse', speed: 'vitesse',
Stock: 'de base', // Thermal-load abbreviation Stock: 'de base', // Thermal-load abbreviation
strength: null, // Strength in reference to Shield Strength strength: 'force', // Strength in reference to Shield Strength
subtotal: null, subtotal: 'Sous-Total',
'T-Load': 'degrés', // Thermal load abbreviation
time: 'temps', // time it takes to complete something time: 'temps', // time it takes to complete something
tooltips: null, // Tooltips setting - show/hide tooltips: 'infobulles', // Tooltips setting - show/hide
Trader: null, // Trader role 'total range': 'plage totale',
Trader: 'commerçant', // Trader role
'unit cost': 'coût unitaire',
unladen: 'Non chargé', // No cargo or fuel unladen: 'Non chargé', // No cargo or fuel
'utility mounts': 'Support utilitaire',
WEP: 'ARM', // Abbreviation - Weapon recharge rate for power distributor WEP: 'ARM', // Abbreviation - Weapon recharge rate for power distributor
yes: 'oui' yes: 'oui'
}; };

View File

@@ -18,90 +18,22 @@ export const terms = {
PHRASE_BACKUP_DESC: 'Сохраните все данные перед переносом в другой браузер или устройство', // Backup of all Coriolis data to save or transfer to another browser/device PHRASE_BACKUP_DESC: 'Сохраните все данные перед переносом в другой браузер или устройство', // Backup of all Coriolis data to save or transfer to another browser/device
PHRASE_CONFIRMATION: 'Вы уверены?', // Are You Sure? PHRASE_CONFIRMATION: 'Вы уверены?', // Are You Sure?
PHRASE_EXPORT_DESC: 'Детальный JSON-экспорт вашей сборки для использования в других местах и инструментах', // A detailed JSON export of your build for use in other sites and tools PHRASE_EXPORT_DESC: 'Детальный JSON-экспорт вашей сборки для использования в других местах и инструментах', // A detailed JSON export of your build for use in other sites and tools
PHRASE_FASTEST_RANGE: null, // Consecutive max range jumps PHRASE_FASTEST_RANGE: 'Последовательные прыжки максимальной дальности', // Consecutive max range jumps
PHRASE_IMPORT: 'Для импорта вставьте код в эту форму', // Paste JSON or import here PHRASE_IMPORT: 'Для импорта вставьте код в эту форму', // Paste JSON or import here
PHRASE_LADEN: null, // Ship Mass + Fuel + Cargo PHRASE_LADEN: 'Масса корабля с учётом топлива и грузов', // Ship Mass + Fuel + Cargo
PHRASE_NO_BUILDS: 'Нечего сравнивать', // No builds added to comparison! PHRASE_NO_BUILDS: 'Нечего сравнивать', // No builds added to comparison!
PHRASE_NO_RETROCH: 'нет ранних версий сборкиконфигурации', // No Retrofitting changes PHRASE_NO_RETROCH: 'нет ранних версий сборки\\конфигурации', // No Retrofitting changes
PHRASE_SELECT_BUILDS: 'Выберите конфигурацию для сравнения', // Select Builds to Compare PHRASE_SELECT_BUILDS: 'Выберите конфигурацию для сравнения', // Select Builds to Compare
PHRASE_SG_RECHARGE: null, // Time from 50% to 100% Charge PHRASE_SG_RECHARGE: 'восстановление с 60% до 100% объема щита', // Time from 50% to 100% Charge
PHRASE_SG_RECOVER: null, // Recovery (to 50%) after collapse PHRASE_SG_RECOVER: 'восстановление [до 60%] после снятия щита', // Recovery (to 50%) after collapse
PHRASE_UNLADEN: null, // Ship Mass excluding Fuel and Cargo PHRASE_UNLADEN: 'Масса корабля без учета топлива и грузов', // Ship Mass excluding Fuel and Cargo
PHRASE_UPDATE_RDY: 'Доступно обновление. Нажмите для обновления.', // Update Available! Click to Refresh PHRASE_UPDATE_RDY: 'Доступно обновление. Нажмите для обновления.', // Update Available! Click to Refresh
// Modules / Module Types - These should match the in-game translation (if any)
'Advanced Discovery Scanner': null, // Advanced Discovery Scanner
'Basic Discovery Scanner': 'Стандартный исследовательский сканер', // Basic Discovery Scanner
'Cargo Hatch': 'Грузовой люк', // Cargo Hatch
'Chaff Launcher': 'Постановщик помех', // Chaff Launcher
'Detailed Surface Scanner': 'Подробный сканер поверхности', // Detailed Surface Scanner
'Electronic Countermeasure': 'Электронная противомера', // Electronic Countermeasure
'Heat Sink Launcher': 'Теплоотводная ПУ', // Heat Sink Launcher
'Intermediate Discovery Scanner': 'Средний исследовательский сканер', // Intermediate Discovery Scanner
'Point Defence': 'Противоракетная защита',
'Standard Docking Computer': 'Стандартный стыковочный компьютер', // Standard Docking Computer
am: 'Ремонтный модуль', // Auto Field-Maintenance Unit
bh: 'Корпус', // Bulkheads
bl: 'Лучевой лазер', // Beam Laser
bsg: null, // 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', // FSD Interdictor
fs: 'Топливосборщик', // Fuel Scoop
fsd: 'Двигатель 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: null, // Planetary Approach Suite
pc: 'Контроллер "Дрон-исследователь"', // Prospector Limpet Controller
pd: 'Распределитель энергии', // power distributor
pl: 'Импульсный лазер', // Pulse Laser
pp: 'Реактор', // Power Plant
psg: 'Генератор призматического щита', // Prismatic Shield Generator
pv: null, // Planetary Vehicle Hanger
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: 'FSD Сканнер', // Frame Shift Wake Scanner
// Bulkheads - These should match the in-game translation (if any)
'Lightweight Alloy': 'Легкий сплав',
'Reinforced Alloy': 'Усиленный сплав',
'Military Grade Composite': 'Военный композит',
'Mirrored Surface Composite': 'Зеркальный композит',
'Reactive Surface Composite': 'Динамическая защита',
// Units / Metrics // Units / Metrics
'/min': null, // Per minute '/s': '/с', // Per second
'/s': null, // Per second
kg: null, // Kilogram
'kg/s': null, // Kilograms per second
km: null, // Kilometer
'm/s': 'м/с', // Meters / Second 'm/s': 'м/с', // Meters / Second
Ls: 'Св.сек', // Light seconds Ls: 'Св.сек', // Light seconds
LY: 'Св.лет', // Light Years LY: 'Св.лет', // Light Years
MJ: null, // Mega Joules
MW: null, // Mega Watts
CR: 'кр.', // Credits abbreviation CR: 'кр.', // Credits abbreviation
// Sizes // Sizes
@@ -110,24 +42,25 @@ export const terms = {
L: 'б', // Large Hardpoint size (single character) L: 'б', // Large Hardpoint size (single character)
H: 'O', // Huge Hardpoint size (single character) H: 'O', // Huge Hardpoint size (single character)
U: 'B', // Utility Hardpoint size (single character) - Kill warrant scanner, etc U: 'B', // Utility Hardpoint size (single character) - Kill warrant scanner, etc
small: 'Малый', // Small ship size
medium: 'Средний', // Medium ship size
large: 'большой', // Large Ship Size
// Insurance // Insurance
alpha: 'Альфа', // Alpha backer insurance level alpha: 'Альфа', // Alpha backer insurance level
beta: 'Бета', // Beta back insurance level beta: 'Бета', // Beta back insurance level
standard: 'Стандартный', // Standard insurance level standard: 'Стандартный', // Standard insurance level
// Terms // Terms
'build name': 'название сборки', // Ship build/configuration/design name 'build name': 'название сборки', // Ship build/configuration/design name
'compare all': 'сравнить все', 'compare all': 'сравнить все',
'create new': 'Создать новый', 'create new': 'Создать новый',
'damage per second': null, 'damage per second': 'урон в секунду',
'delete all': 'Удалить все', 'delete all': 'Удалить все',
'detailed export': 'Подробный экспорт', 'detailed export': 'Подробный экспорт',
'edit data': 'Редактирование', 'edit data': 'Редактирование',
'empty all': null, 'empty all': 'пусто все',
'Enter Name': 'Введите имя', 'Enter Name': 'Введите имя',
'fastest range': null, // Fastet totaljump range - sum of succesive jumps 'fastest range': 'быстрый диапазон', // Fastet totaljump range - sum of succesive jumps
'fuel level': null, // Percent of fuel (T) in the tank 'fuel level': 'уровень топлива', // Percent of fuel (T) in the tank
'full tank': 'Полный бак', 'full tank': 'Полный бак',
'internal compartments': 'внутренние отсеки', 'internal compartments': 'внутренние отсеки',
'jump range': 'Дальность прыжка', 'jump range': 'Дальность прыжка',
@@ -135,24 +68,18 @@ export const terms = {
'max mass': 'Максимальная масса', 'max mass': 'Максимальная масса',
'net cost': 'разница в цене', 'net cost': 'разница в цене',
'none created': 'не создано', 'none created': 'не создано',
'optimal mass': null, // Lowest weight / best weight for jump distance, etc
'refuel time': 'Время дозаправки', // Time to refuel the tank when scooping 'refuel time': 'Время дозаправки', // Time to refuel the tank when scooping
'reload costs': null,
'retrofit costs': 'цена модификации', // The cost difference when upgrading / downgrading a component 'retrofit costs': 'цена модификации', // The cost difference when upgrading / downgrading a component
'retrofit from': 'модификация от', // Retrofit from Build A against build B 'retrofit from': 'модификация от', // Retrofit from Build A against build B
'T-Load': 'Тепл.', // Thermal load abbreviation 'T-Load': 'Тепл.', // Thermal load abbreviation
'total range': null,
'unit cost': null,
'utility mounts': 'Вспомогательное оборудование', 'utility mounts': 'Вспомогательное оборудование',
about: 'О ...', // Link to about page / about Coriolis.io about: 'О ...', // Link to about page / about Coriolis.io
action: 'Действие', action: 'Действие',
added: 'Добавлено', added: 'Добавлено',
ammo: 'Боекомплект', // Ammunition ammo: 'Боекомплект', // Ammunition
armour: 'Броня', armour: 'Броня',
available: 'доступно', available: 'доступно', // Available options
backup: 'Резервная копия', backup: 'Резервная копия',
base: null,
bays: null,
bins: 'контейнеры', // Number of Mining Refinery bins bins: 'контейнеры', // Number of Mining Refinery bins
boost: 'форсаж', boost: 'форсаж',
build: 'cборка', // Shorthand for the build/configuration/design name build: 'cборка', // Shorthand for the build/configuration/design name
@@ -179,7 +106,6 @@ export const terms = {
efficiency: 'Эффективность', // Power Plant efficiency efficiency: 'Эффективность', // Power Plant efficiency
empty: 'пусто', empty: 'пусто',
ENG: 'ДВГ', // Abbreviation - Engine recharge rate for power distributor ENG: 'ДВГ', // Abbreviation - Engine recharge rate for power distributor
Explorer: null,
export: 'Экспорт', export: 'Экспорт',
forum: 'Форум', forum: 'Форум',
fuel: 'Топливо', fuel: 'Топливо',
@@ -187,32 +113,26 @@ export const terms = {
hull: 'Корпус', // Ships hull hull: 'Корпус', // Ships hull
import: 'импортировать ', import: 'импортировать ',
insurance: 'Страховка', insurance: 'Страховка',
jump: null, // Single jump range
jumps: 'Прыжков', jumps: 'Прыжков',
laden: 'Груженый', laden: 'Груженый',
language: 'Язык', language: 'Язык',
maneuverability: 'Моневренность', maneuverability: 'Маневренность',
manufacturer: null,
mass: 'Масса', mass: 'Масса',
max: 'Макс', max: 'Макс',
MLF: null, // Mass Lock Factor Abbreviation
MNV: null, // Maneuverability abbreviation
module: null,
modules: null,
no: 'Нет', no: 'Нет',
ok: null,
optimize: null,
pen: 'ПБ', // Armour peneration abbreviation pen: 'ПБ', // Armour peneration abbreviation
permalink: 'Постоянная ссылка', permalink: 'Постоянная ссылка',
power: 'Мощность', // Power = Energy / second. Power generated by the power plant, or power consumed (MW / Mega Watts). Used in the power plant section 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 pri: 'Осн', // Priority abbreviation for power management
proceed: 'продолжить', proceed: 'продолжить',
PWR: 'Эн', // Power Abbreviation. See Power PWR: 'Эн', // Power Abbreviation. See Power
qty: null, // Quantity abbreviation
range: 'Дальность', range: 'Дальность',
rate: 'скорость', rate: 'скорость',
recharge: null, // Shield Recharge time from 50% -> 100% recharge: 'перезарядка', // Shield Recharge time from 50% -> 100%
recovery: null, // Shield recovery time (after losing shields/turning on -> 50%) recovery: 'включение', // Shield recovery time (after losing shields/turning on -> 50%)
reload: 'Перезагрузить', // Reload weapon/hardpoint reload: 'Перезагрузить', // Reload weapon/hardpoint
rename: 'Переименовать', rename: 'Переименовать',
repair: 'Починка', repair: 'Починка',
@@ -220,7 +140,7 @@ export const terms = {
ret: 'Убр.', // Retracted abbreviation ret: 'Убр.', // Retracted abbreviation
retracted: 'Убрано', // Weapons/Hardpoints retracted retracted: 'Убрано', // Weapons/Hardpoints retracted
ROF: 'В/сек', // Rate of Fire abbreviation ROF: 'В/сек', // Rate of Fire abbreviation
roles: null, // Commander/Ship build roles - e.g. Trader, Bounty-Hunter, Explorer, etc
save: 'Сохранить', save: 'Сохранить',
sell: 'Продать', sell: 'Продать',
settings: 'Настройки', // Coriolis application settings settings: 'Настройки', // Coriolis application settings
@@ -233,13 +153,9 @@ export const terms = {
speed: 'скорость', speed: 'скорость',
standard: 'Стандартный', // Standard / Common modules (FSD, power plant, life support, etc) standard: 'Стандартный', // Standard / Common modules (FSD, power plant, life support, etc)
Stock: 'Стандартная комплектация', // Thermal-load abbreviation Stock: 'Стандартная комплектация', // Thermal-load abbreviation
strength: null, // Strength in reference to Shield Strength
subtotal: null,
SYS: 'СИСТЕМЫ', // Abbreviation - System recharge rate for power distributor SYS: 'СИСТЕМЫ', // Abbreviation - System recharge rate for power distributor
time: 'Время', // time it takes to complete something time: 'Время', // time it takes to complete something
tooltips: null, // Tooltips setting - show/hide
total: 'Всего', total: 'Всего',
Trader: null, // Trader role
type: 'Тип', type: 'Тип',
unladen: 'Пустой', // No cargo or fuel unladen: 'Пустой', // No cargo or fuel
URL: 'Ссылка', // Link, Uniform Resource Locator URL: 'Ссылка', // Link, Uniform Resource Locator

View File

@@ -24,15 +24,17 @@ export default class AboutPage extends Page {
*/ */
renderPage() { renderPage() {
return <div className={'page'} style={{ textAlign: 'left', maxWidth: 800, margin: '0 auto' }}> return <div className={'page'} style={{ textAlign: 'left', maxWidth: 800, margin: '0 auto' }}>
<h1><CoriolisLogo style={{ marginRight: '0.4em' }} className='xl'/><span className='warning'>Coriolis</span></h1> <h1><CoriolisLogo style={{ marginRight: '0.4em' }} className='xl'/><span className='warning'>Coriolis EDCD Edition</span></h1>
<p>This is a clone of the Coriolis project, whose original author is currently unable to maintain it. This clone is maintained by the <a href="http://edcd.github.io/">EDCD community</a>.</p>
<p>To recover your builds, go to <a href='https://coriolis.io/' target='_blank'>https://coriolis.io/</a>, backup your builds (Settings / Backup), copy the text, return here and import (Settings / Import).</p>
<p>The Coriolis project was inspired by <a href='http://www.edshipyard.com/' target='_blank'>E:D Shipyard</a> and, of course, <a href='http://www.elitedangerous.com' target='_blank'>Elite Dangerous</a>. The ultimate goal of Coriolis is to provide rich features to support in-game play and planning while engaging the E:D community to support its development.</p> <p>The Coriolis project was inspired by <a href='http://www.edshipyard.com/' target='_blank'>E:D Shipyard</a> and, of course, <a href='http://www.elitedangerous.com' target='_blank'>Elite Dangerous</a>. The ultimate goal of Coriolis is to provide rich features to support in-game play and planning while engaging the E:D community to support its development.</p>
<p>Coriolis was created using assets and imagery from Elite: Dangerous, with the permission of Frontier Developments plc, for non-commercial purposes. It is not endorsed by nor reflects the views or opinions of Frontier Developments. A number of assets were sourced from <a href='http://edassets.org' target='_blank'>ED Assets</a></p>
<p>Coriolis was created using assets and imagery from Elite: Dangerous, with the permission of Frontier Developments plc, for non-commercial purposes. It is not endorsed by nor reflects the views or opinions of Frontier Developments.</p> <a style={{ display: 'block', textDecoration: 'none' }} href='https://github.com/EDCD/coriolis' target='_blank' title='Coriolis Github Project'>
<a style={{ display: 'block', textDecoration: 'none' }} href='https://github.com/cmmcleod/coriolis' target='_blank' title='Coriolis Github Project'>
<GitHub style={{ margin: '0.4em' }} className='l fg xl'/> <GitHub style={{ margin: '0.4em' }} className='l fg xl'/>
<h2 style={{ margin: 0, textDecoration: 'none' }}>Github</h2> <h2 style={{ margin: 0, textDecoration: 'none' }}>Github</h2>
github.com/cmmcleod/coriolis github.com/EDCD/coriolis
</a> </a>
<p>Coriolis is an open source project. Checkout the list of upcoming features and to-do list on github. Any and all contributions and feedback are welcome. If you encounter any bugs please report them and provide as much detail as possible.</p> <p>Coriolis is an open source project. Checkout the list of upcoming features and to-do list on github. Any and all contributions and feedback are welcome. If you encounter any bugs please report them and provide as much detail as possible.</p>
@@ -47,7 +49,7 @@ export default class AboutPage extends Page {
<p>Help keep the lights on! Donations will be used to cover costs of running and maintaining Coriolis. Thanks for helping!</p> <p>Help keep the lights on! Donations will be used to cover costs of running and maintaining Coriolis. Thanks for helping!</p>
<h3>Chat</h3> <h3>Chat</h3>
<p>You can chat to us on <a href='https://www.hipchat.com/gfYQiZcmy' target='_blank'>HipChat</a> and sign up to chat on all E:D HipChat groups <a href='https://elite-dangerous.hipchat.com/invite/74670/a3af7ea57008362d83d05fada09bdf84' target='_blank'>here</a>.</p> <p>You can chat to us on our <a href='https://discord.gg/0uwCh6R62aPRjk9w' target='_blank'>EDCD Discord server</a>.</p>
</div>; </div>;
} }
} }

View File

@@ -53,6 +53,7 @@ export default class ComparisonPage extends Page {
super(props, context); super(props, context);
this._sortShips = this._sortShips.bind(this); this._sortShips = this._sortShips.bind(this);
this._buildsSelected = this._buildsSelected.bind(this); this._buildsSelected = this._buildsSelected.bind(this);
this._updateDiscounts = this._updateDiscounts.bind(this);
this.state = this._initState(context); this.state = this._initState(context);
} }
@@ -130,7 +131,7 @@ export default class ComparisonPage extends Page {
builds.sort(sortBy(predicate)); builds.sort(sortBy(predicate));
return { return {
title: 'Coriolis - Compare', title: 'Coriolis EDCD Edition - Compare',
predicate, predicate,
desc, desc,
facets, facets,
@@ -161,6 +162,7 @@ export default class ComparisonPage extends Page {
let b = new Ship(id, data.properties, data.slots); // Create a new Ship instance let b = new Ship(id, data.properties, data.slots); // Create a new Ship instance
b.buildFrom(code); // Populate components from code b.buildFrom(code); // Populate components from code
b.buildName = name; b.buildName = name;
b.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
return b; return b;
}; };
@@ -383,6 +385,21 @@ export default class ComparisonPage extends Page {
}); });
} }
/**
* Update all ship costs on disount change
*/
_updateDiscounts() {
let shipDiscount = Persist.getShipDiscount();
let moduleDiscount = Persist.getModuleDiscount();
let builds = [];
for (let b of this.state.builds) {
builds.push(b.applyDiscounts(shipDiscount, moduleDiscount));
}
this.setState({ builds });
}
/** /**
* Update state based on context changes * Update state based on context changes
* @param {Object} nextProps Incoming/Next properties * @param {Object} nextProps Incoming/Next properties
@@ -399,6 +416,7 @@ export default class ComparisonPage extends Page {
*/ */
componentWillMount() { componentWillMount() {
this.resizeListener = this.context.onWindowResize(this._updateDimensions); this.resizeListener = this.context.onWindowResize(this._updateDimensions);
this.persistListener = Persist.addListener('discounts', this._updateDiscounts);
} }
/** /**
@@ -413,6 +431,7 @@ export default class ComparisonPage extends Page {
*/ */
componentWillUnmount() { componentWillUnmount() {
this.resizeListener.remove(); this.resizeListener.remove();
this.persistListener.remove();
} }
/** /**

View File

@@ -24,6 +24,16 @@ import Slider from '../components/Slider';
const SPEED_SERIES = ['boost', '4 Pips', '2 Pips', '0 Pips']; const SPEED_SERIES = ['boost', '4 Pips', '2 Pips', '0 Pips'];
const SPEED_COLORS = ['#0088d2', '#ff8c0d', '#D26D00', '#c06400']; const SPEED_COLORS = ['#0088d2', '#ff8c0d', '#D26D00', '#c06400'];
/**
* Document Title Generator
* @param {String} shipName Ship Name
* @param {String} buildName Build Name
* @return {String} Document title
*/
function getTitle(shipName, buildName) {
return `${shipName}${buildName ? ` - ${buildName}` : ''}`;
}
/** /**
* The Outfitting Page * The Outfitting Page
*/ */
@@ -65,10 +75,11 @@ export default class OutfittingPage extends Page {
} }
let fuelCapacity = ship.fuelCapacity; let fuelCapacity = ship.fuelCapacity;
this._getTitle = getTitle.bind(this, data.properties.name);
return { return {
error: null, error: null,
title: 'Outfitting - ' + data.properties.name, title: this._getTitle(buildName),
costTab: Persist.getCostTab() || 'costs', costTab: Persist.getCostTab() || 'costs',
buildName, buildName,
newBuildName: buildName, newBuildName: buildName,
@@ -117,7 +128,7 @@ export default class OutfittingPage extends Page {
this._updateRoute(shipId, newBuildName, code); this._updateRoute(shipId, newBuildName, code);
} }
this.setState({ buildName: newBuildName, code, savedCode: code }); this.setState({ buildName: newBuildName, code, savedCode: code, title: this._getTitle(newBuildName) });
} }
/** /**
@@ -282,7 +293,7 @@ export default class OutfittingPage extends Page {
<div id='overview'> <div id='overview'>
<h1>{ship.name}</h1> <h1>{ship.name}</h1>
<div id='build'> <div id='build'>
<input value={newBuildName} onChange={this._buildNameChange} placeholder={translate('Enter Name')} maxsize={50} /> <input value={newBuildName} onChange={this._buildNameChange} placeholder={translate('Enter Name')} maxLength={50} />
<button onClick={canSave && this._saveBuild} disabled={!canSave} onMouseOver={termtip.bind(null, 'save')} onMouseOut={hide}> <button onClick={canSave && this._saveBuild} disabled={!canSave} onMouseOver={termtip.bind(null, 'save')} onMouseOut={hide}>
<FloppyDisk className='lg' /> <FloppyDisk className='lg' />
</button> </button>

View File

@@ -8,17 +8,18 @@ import { shallowEqual } from '../utils/UtilityFunctions';
export default class Page extends React.Component { export default class Page extends React.Component {
static contextTypes = { static contextTypes = {
route: React.PropTypes.object.isRequired,
language: React.PropTypes.object.isRequired,
sizeRatio: React.PropTypes.number.isRequired,
openMenu: React.PropTypes.func.isRequired,
closeMenu: React.PropTypes.func.isRequired, closeMenu: React.PropTypes.func.isRequired,
showModal: React.PropTypes.func.isRequired,
hideModal: React.PropTypes.func.isRequired, hideModal: React.PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired, language: React.PropTypes.object.isRequired,
termtip: React.PropTypes.func.isRequired, noTouch: React.PropTypes.bool.isRequired,
onCommand: React.PropTypes.func.isRequired,
onWindowResize: React.PropTypes.func.isRequired, onWindowResize: React.PropTypes.func.isRequired,
onCommand: React.PropTypes.func.isRequired openMenu: React.PropTypes.func.isRequired,
route: React.PropTypes.object.isRequired,
showModal: React.PropTypes.func.isRequired,
sizeRatio: React.PropTypes.number.isRequired,
termtip: React.PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired
}; };
static propTypes = { static propTypes = {

167
src/app/pages/ShipyardPage.jsx Executable file → Normal file
View File

@@ -27,6 +27,38 @@ function countInt(slot) {
this.maxCargo += crEligible ? ModuleUtils.findInternal('cr', slot.maxClass, 'E').cargo : 0; this.maxCargo += crEligible ? ModuleUtils.findInternal('cr', slot.maxClass, 'E').cargo : 0;
} }
/**
* Generate Ship summary and aggregated properties
* @param {String} shipId Ship Id
* @param {Object} shipData Ship Default Data
* @return {Object} Ship summary and aggregated properties
*/
function shipSummary(shipId, shipData) {
let summary = {
id: shipId,
hpCount: 0,
intCount: 0,
maxCargo: 0,
hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge
int: [0, 0, 0, 0, 0, 0, 0, 0] // Sizes 1 - 8
};
Object.assign(summary, shipData.properties);
let ship = new Ship(shipId, shipData.properties, shipData.slots);
// Build Ship
ship.buildWith(shipData.defaults); // Populate with stock/default components
ship.hardpoints.forEach(countHp.bind(summary)); // Count Hardpoints by class
ship.internal.forEach(countInt.bind(summary)); // Count Internal Compartments by class
summary.retailCost = ship.totalCost; // Record Stock/Default/retail cost
ship.optimizeMass({ pd: '1D' }); // Optimize Mass with 1D PD for maximum possible jump range
summary.maxJumpRange = ship.unladenRange; // Record Jump Range
ship.optimizeMass({ th: ship.standard[1].maxClass + 'A', fsd: '2D', ft: '1C' }); // Optmize mass with Max Thrusters
summary.topSpeed = ship.topSpeed;
summary.topBoost = ship.topBoost;
return summary;
}
/** /**
* The Shipyard summary page * The Shipyard summary page
*/ */
@@ -41,20 +73,30 @@ export default class ShipyardPage extends Page {
*/ */
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
this.state = {
title: 'Coriolis - Shipyard',
shipPredicate: 'name',
shipDesc: true
};
if (!ShipyardPage.cachedShipSummaries) { if (!ShipyardPage.cachedShipSummaries) {
ShipyardPage.cachedShipSummaries = []; ShipyardPage.cachedShipSummaries = [];
for (let s in Ships) { for (let s in Ships) {
ShipyardPage.cachedShipSummaries.push(this._shipSummary(s, Ships[s])); ShipyardPage.cachedShipSummaries.push(shipSummary(s, Ships[s]));
} }
} }
this.shipSummaries = ShipyardPage.cachedShipSummaries; this.state = {
title: 'Coriolis EDCD Edition - Shipyard',
shipPredicate: 'name',
shipDesc: true,
shipSummaries: ShipyardPage.cachedShipSummaries
};
}
/**
* Higlight the current ship in the table
* @param {String} shipId Ship Id
* @param {SyntheticEvent} event Event
*/
_highlightShip(shipId, event) {
event.stopPropagation();
this.setState({ shipId });
} }
/** /**
@@ -76,38 +118,6 @@ export default class ShipyardPage extends Page {
this.setState({ shipPredicate, shipDesc, shipPredicateIndex }); this.setState({ shipPredicate, shipDesc, shipPredicateIndex });
}; };
/**
* Generate Ship summary and aggregated properties
* @param {String} shipId Ship Id
* @param {Object} shipData Ship Default Data
* @return {Object} Ship summary and aggregated properties
*/
_shipSummary(shipId, shipData) {
let summary = {
id: shipId,
hpCount: 0,
intCount: 0,
maxCargo: 0,
hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge
int: [0, 0, 0, 0, 0, 0, 0, 0] // Sizes 1 - 8
};
Object.assign(summary, shipData.properties);
let ship = new Ship(shipId, shipData.properties, shipData.slots);
// Build Ship
ship.buildWith(shipData.defaults); // Populate with stock/default components
ship.hardpoints.forEach(countHp.bind(summary)); // Count Hardpoints by class
ship.internal.forEach(countInt.bind(summary)); // Count Internal Compartments by class
summary.retailCost = ship.totalCost; // Record Stock/Default/retail cost
ship.optimizeMass({ pd: '1D' }); // Optimize Mass with 1D PD for maximum possible jump range
summary.maxJumpRange = ship.unladenRange; // Record Jump Range
ship.optimizeMass({ th: ship.standard[1].maxClass + 'A' }); // Optmize mass with Max Thrusters
summary.topSpeed = ship.topSpeed;
summary.topBoost = ship.topBoost;
return summary;
}
/** /**
* Generate the table row summary for the ship * Generate the table row summary for the ship
* @param {Object} s Ship summary * @param {Object} s Ship summary
@@ -118,8 +128,14 @@ export default class ShipyardPage extends Page {
* @return {React.Component} Table Row * @return {React.Component} Table Row
*/ */
_shipRowElement(s, translate, u, fInt, fRound) { _shipRowElement(s, translate, u, fInt, fRound) {
return <tr key={s.id} className='highlight'> let noTouch = this.context.noTouch;
<td className='le'><Link href={'/outfit/' + s.id}>{s.name}</Link></td>
return <tr
key={s.id}
style={{ height: '1.5em' }}
className={cn({ highlighted: noTouch && this.state.shipId === s.id })}
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
>
<td className='le'>{s.manufacturer}</td> <td className='le'>{s.manufacturer}</td>
<td className='cap'>{translate(SizeMap[s.class])}</td> <td className='cap'>{translate(SizeMap[s.class])}</td>
<td>{s.agility}</td> <td>{s.agility}</td>
@@ -155,17 +171,27 @@ export default class ShipyardPage extends Page {
* @return {React.Component} The page contents * @return {React.Component} The page contents
*/ */
renderPage() { renderPage() {
let { translate, formats, units } = this.context.language; let { sizeRatio, language, termtip, noTouch } = this.context;
let { translate, formats, units } = language;
let hide = this.context.tooltip.bind(null, null);
let fInt = formats.int; let fInt = formats.int;
let fRound = formats.round; let fRound = formats.round;
let shipSummaries = this.shipSummaries; let { shipSummaries, shipPredicate, shipPredicateIndex } = this.state;
let shipPredicate = this.state.shipPredicate;
let shipPredicateIndex = this.state.shipPredicateIndex;
let shipRows = [];
let hide = this.context.tooltip.bind(null, null);
let tip = this.context.termtip;
let sortShips = (predicate, index) => this._sortShips.bind(this, predicate, index); let sortShips = (predicate, index) => this._sortShips.bind(this, predicate, index);
let filters = {
// 'class': { 1: 1, 2: 1}
};
shipSummaries = shipSummaries.filter(s => {
for (let prop in filters) {
if (!(s[prop] in filters[prop])) {
return false;
}
}
return true;
});
// Sort shipsOverview // Sort shipsOverview
shipSummaries.sort((a, b) => { shipSummaries.sort((a, b) => {
let valA = a[shipPredicate], valB = b[shipPredicate]; let valA = a[shipPredicate], valB = b[shipPredicate];
@@ -194,26 +220,52 @@ export default class ShipyardPage extends Page {
} }
}); });
let i = 0;
let shipRows = new Array(shipSummaries.length);
let detailRows = new Array(shipSummaries.length);
for (let s of shipSummaries) { for (let s of shipSummaries) {
shipRows.push(this._shipRowElement(s, translate, units, fInt, fRound)); detailRows[i] = this._shipRowElement(s, translate, units, fInt, fRound);
shipRows[i] = (
<tr
key={i}
style={{ height: '1.5em' }}
className={cn({ highlighted: noTouch && this.state.shipId === s.id })}
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
>
<td className='le'><Link href={'/outfit/' + s.id}>{s.name}</Link></td>
</tr>
);
i++;
} }
return ( return (
<div className='page' style={{ fontSize: this.context.sizeRatio + 'em' }}> <div className='page' style={{ fontSize: sizeRatio + 'em' }}>
<div className='scroll-x'> <p style={{ textAlign: 'center' }}>This is <strong>Coriolis EDCD Edition</strong> - a temporary clone of <a href='https://coriolis.io/' target='_blank'>https://coriolis.io/</a> with added support for E:D 2.2. For more info see Settings / <Link href="/about" className='block'>About</Link></p>
<table style={{ fontSize:'0.85em', whiteSpace:'nowrap', margin: '0 auto' }} align='center'> <div style={{ whiteSpace: 'nowrap', margin: '0 auto', fontSize: '0.8em', position: 'relative', display: 'inline-block', maxWidth: '100%' }}>
<table style={{ width: '12em', position: 'absolute', zIndex: 1 }}>
<thead>
<tr className='main'>
<th style={{ height: '2.6em', padding: '2px 0.4em 1px' }} className='sortable le rgt' onClick={sortShips('name')}>{translate('ship')}</th>
</tr>
</thead>
<tbody onMouseLeave={this._highlightShip.bind(this, null)}>
{shipRows}
</tbody>
</table>
<div style={{ overflowX: 'scroll', maxWidth: '100%' }}>
<table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }}>
<thead> <thead>
<tr className='main'> <tr className='main'>
<th rowSpan={2} className='sortable le' onClick={sortShips('name')}>{translate('ship')}</th>
<th rowSpan={2} className='sortable' onClick={sortShips('manufacturer')}>{translate('manufacturer')}</th> <th rowSpan={2} className='sortable' onClick={sortShips('manufacturer')}>{translate('manufacturer')}</th>
<th rowSpan={2} className='sortable' onClick={sortShips('class')}>{translate('size')}</th> <th rowSpan={2} className='sortable' onClick={sortShips('class')}>{translate('size')}</th>
<th rowSpan={2} className='sortable' onMouseEnter={tip.bind(null, 'maneuverability')} onMouseLeave={hide} onClick={sortShips('agility')}>{translate('mnv')}</th> <th rowSpan={2} className='sortable' onMouseEnter={termtip.bind(null, 'maneuverability')} onMouseLeave={hide} onClick={sortShips('agility')}>{translate('mnv')}</th>
<th colSpan={4}>{translate('base')}</th> <th colSpan={4}>{translate('base')}</th>
<th colSpan={4}>{translate('max')}</th> <th colSpan={4}>{translate('max')}</th>
<th colSpan={5} className='sortable' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th> <th colSpan={5} className='sortable' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th>
<th colSpan={8} className='sortable' onClick={sortShips('intCount')}>{translate('internal compartments')}</th> <th colSpan={8} className='sortable' onClick={sortShips('intCount')}>{translate('internal compartments')}</th>
<th rowSpan={2} className='sortable' onClick={sortShips('hullMass')}>{translate('hull')}</th> <th rowSpan={2} className='sortable' onClick={sortShips('hullMass')}>{translate('hull')}</th>
<th rowSpan={2} className='sortable' onMouseEnter={tip.bind(null, 'mass lock factor')} onMouseLeave={hide} onClick={sortShips('masslock')} >{translate('MLF')}</th> <th rowSpan={2} className='sortable' onMouseEnter={termtip.bind(null, 'mass lock factor')} onMouseLeave={hide} onClick={sortShips('masslock')} >{translate('MLF')}</th>
<th rowSpan={2} className='sortable' onClick={sortShips('retailCost')}>{translate('cost')}</th> <th rowSpan={2} className='sortable' onClick={sortShips('retailCost')}>{translate('cost')}</th>
</tr> </tr>
<tr> <tr>
@@ -243,10 +295,11 @@ export default class ShipyardPage extends Page {
<th className='sortable' onClick={sortShips('int', 7)} >8</th> <th className='sortable' onClick={sortShips('int', 7)} >8</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody onMouseLeave={this._highlightShip.bind(this, null)}>
{shipRows} {detailRows}
</tbody> </tbody>
</table> </table>
</div>
</div> </div>
</div> </div>
); );

View File

@@ -37,17 +37,22 @@ export const ModuleGroupToName = {
am: 'Auto Field-Maintenance Unit', am: 'Auto Field-Maintenance Unit',
bsg: 'Bi-Weave Shield Generator', bsg: 'Bi-Weave Shield Generator',
cr: 'Cargo Rack', cr: 'Cargo Rack',
fh: 'Fighter Hangar',
fi: 'Frame Shift Drive Interdictor', fi: 'Frame Shift Drive Interdictor',
hb: 'Hatch Breaker Limpet Controller', hb: 'Hatch Breaker Limpet Controller',
hr: 'Hull Reinforcement Package', hr: 'Hull Reinforcement Package',
rf: 'Refinery', rf: 'Refinery',
scb: 'Shield Cell Bank', scb: 'Shield Cell Bank',
sg: 'Shield Generator', sg: 'Shield Generator',
pv: 'Planetary Vehicle Hanger', pv: 'Planetary Vehicle Hangar',
psg: 'Prismatic Shield Generator', psg: 'Prismatic Shield Generator',
dc: 'Docking Computer', dc: 'Docking Computer',
fx: 'Fuel Transfer Limpet Controller', fx: 'Fuel Transfer Limpet Controller',
pc: 'Prospector Limpet Controller', pc: 'Prospector Limpet Controller',
pce: 'Economy Class Passenger Cabin',
pci: 'Business Class Passenger Cabin',
pcm: 'First Class Passenger Cabin',
pcq: 'Luxury Passenger Cabin',
cc: 'Collector Limpet Controller', cc: 'Collector Limpet Controller',
// Hard Points // Hard Points

View File

@@ -1,4 +1,6 @@
import { BulkheadNames } from './Constants';
/** /**
* Filter eligble modules based on parameters * Filter eligble modules based on parameters
* @param {Array} arr Available modules array * @param {Array} arr Available modules array
@@ -19,12 +21,13 @@ export default class ModuleSet {
/** /**
* Instantiate the module set * Instantiate the module set
* @param {Object} modules All Modules * @param {Object} modules All Modules
* @param {number} mass Ship mass * @param {Object} shipData Ship Specifications Data (see coriolis-data/Ships)
* @param {Array} maxStandardArr Array of standard slots classes/sizes
* @param {Array} maxInternal Array of internal slots classes/sizes
* @param {Array} maxHardPoint Array of hardpoint slots classes/sizes
*/ */
constructor(modules, mass, maxStandardArr, maxInternal, maxHardPoint) { constructor(modules, shipData) {
let maxInternal = isNaN(shipData.slots.internal[0]) ? shipData.slots.internal[0].class : shipData.slots.internal[0];
let mass = shipData.properties.hullMass + 6.5;
let maxStandardArr = shipData.slots.standard;
let maxHardPoint = shipData.slots.hardpoints[0];
let stnd = modules.standard; let stnd = modules.standard;
this.mass = mass; this.mass = mass;
this.standard = {}; this.standard = {};
@@ -33,6 +36,10 @@ export default class ModuleSet {
this.hpClass = {}; this.hpClass = {};
this.intClass = {}; this.intClass = {};
this.bulkheads = shipData.bulkheads.map((b, i) => {
return Object.assign({ grp: 'bh', name: BulkheadNames[i], index: i, class: '', rating: '' }, b);
});
this.standard[0] = filter(stnd.pp, maxStandardArr[0], 0, mass); // Power Plant this.standard[0] = filter(stnd.pp, maxStandardArr[0], 0, mass); // Power Plant
this.standard[2] = filter(stnd.fsd, maxStandardArr[2], 0, mass); // FSD this.standard[2] = filter(stnd.fsd, maxStandardArr[2], 0, mass); // FSD
this.standard[4] = filter(stnd.pd, maxStandardArr[4], 0, mass); // Power Distributor this.standard[4] = filter(stnd.pd, maxStandardArr[4], 0, mass); // Power Distributor
@@ -53,18 +60,34 @@ export default class ModuleSet {
} }
} }
/**
* Get the specified bulkhead
* @param {integer} index Bulkhead index
* @return {Object} Bulkhead module details
*/
getBulkhead(index) {
return this.bulkheads[index] || null;
}
/** /**
* Determine the modules that areeligible for an internal slot * Determine the modules that areeligible for an internal slot
* @param {Object} ship The ship
* @param {integer} c The max class module that can be mounted in the slot * @param {integer} c The max class module that can be mounted in the slot
* @param {Object} eligible) The map of eligible internal groups * @param {Object} eligible) The map of eligible internal groups
* @return {object} A map of all eligible modules by group * @return {object} A map of all eligible modules by group
*/ */
getInts(c, eligible) { getInts(ship, c, eligible) {
let o = {}; let o = {};
for (let key in this.internal) { for (let key in this.internal) {
if (eligible && !eligible[key]) { if (eligible && !eligible[key]) {
continue; continue;
} }
if (key == 'pcq' && !(ship.luxuryCabins && ship.luxuryCabins === true)) {
continue;
}
if (key == 'fh' && !(ship.fighterHangars && ship.fighterHangars === true)) {
continue;
}
let data = filter(this.internal[key], c, 0, this.mass); let data = filter(this.internal[key], c, 0, this.mass);
if (data.length) { // If group is not empty if (data.length) { // If group is not empty
o[key] = data; o[key] = data;
@@ -135,7 +158,7 @@ export default class ModuleSet {
let sg = this.internal.sg[0]; let sg = this.internal.sg[0];
for (let s of this.internal.sg) { for (let s of this.internal.sg) {
if (s.mass < sg.mass && s.minmass <= hullMass && s.maxmass > hullMass) { if (s.mass < sg.mass && s.maxmass > hullMass) {
sg = s; sg = s;
} }
} }

View File

@@ -29,7 +29,7 @@ export function standard(type, id) {
/** /**
* Finds the hardpoint with the specified ID * Finds the hardpoint with the specified ID
* @param {string} id Hardpoint ID * @param {String} id Hardpoint ID
* @return {Object} Hardpoint module or null * @return {Object} Hardpoint module or null
*/ */
export function hardpoints(id) { export function hardpoints(id) {
@@ -46,7 +46,7 @@ export function hardpoints(id) {
/** /**
* Finds the internal module with the specified ID * Finds the internal module with the specified ID
* @param {string} id Internal module ID * @param {String} id Internal module ID
* @return {Object} Internal module or null * @return {Object} Internal module or null
*/ */
export function internal(id) { export function internal(id) {
@@ -65,11 +65,11 @@ export function internal(id) {
* Finds an internal module based on Class, Rating, Group and/or name. * Finds an internal module based on Class, Rating, Group and/or name.
* At least one ofGroup name or unique module name must be provided * At least one ofGroup name or unique module name must be provided
* *
* @param {string} groupName [Optional] Full name or abbreviated name for module group * @param {String} groupName [Optional] Full name or abbreviated name for module group
* @param {integer} clss module Class * @param {integer} clss module Class
* @param {string} rating module Rating * @param {String} rating module Rating
* @param {string} name [Optional] Long/unique name for module -e.g. 'Advanced Discover Scanner' * @param {String} name [Optional] Long/unique name for module -e.g. 'Advanced Discover Scanner'
* @return {String} The id of the module if found, null if not found * @return {Object} The module if found, null if not found
*/ */
export function findInternal(groupName, clss, rating, name) { export function findInternal(groupName, clss, rating, name) {
let groups = {}; let groups = {};
@@ -103,10 +103,10 @@ export function findInternal(groupName, clss, rating, name) {
* Finds an internal Module ID based on Class, Rating, Group and/or name. * Finds an internal Module ID based on Class, Rating, Group and/or name.
* At least one ofGroup name or unique module name must be provided * At least one ofGroup name or unique module name must be provided
* *
* @param {string} groupName [Optional] Full name or abbreviated name for module group * @param {String} groupName [Optional] Full name or abbreviated name for module group
* @param {integer} clss module Class * @param {integer} clss module Class
* @param {string} rating Module Rating * @param {String} rating Module Rating
* @param {string} name [Optional] Long/unique name for module -e.g. 'Advanced Discover Scanner' * @param {String} name [Optional] Long/unique name for module -e.g. 'Advanced Discover Scanner'
* @return {String} The id of the module if found, null if not found * @return {String} The id of the module if found, null if not found
*/ */
export function findInternalId(groupName, clss, rating, name) { export function findInternalId(groupName, clss, rating, name) {
@@ -118,12 +118,12 @@ export function findInternalId(groupName, clss, rating, name) {
* Finds a hardpoint Module based on Class, Rating, Group and/or name. * Finds a hardpoint Module based on Class, Rating, Group and/or name.
* At least one ofGroup name or unique module name must be provided * At least one ofGroup name or unique module name must be provided
* *
* @param {string} groupName [Optional] Full name or abbreviated name for module group * @param {String} groupName [Optional] Full name or abbreviated name for module group
* @param {integer} clss Module Class * @param {integer} clss Module Class
* @param {string} rating [Optional] module Rating * @param {String} rating [Optional] module Rating
* @param {string} name [Optional] Long/unique name for module -e.g. 'Heat Sink Launcher' * @param {String} name [Optional] Long/unique name for module -e.g. 'Heat Sink Launcher'
* @param {string} mount Mount type - [F]ixed, [G]imballed, [T]urret * @param {String} mount Mount type - [F]ixed, [G]imballed, [T]urret
* @param {string} missile [Optional] Missile type - [D]umbfire, [S]eeker * @param {String} missile [Optional] Missile type - [D]umbfire, [S]eeker
* @return {String} The id of the module if found, null if not found * @return {String} The id of the module if found, null if not found
*/ */
export function findHardpoint(groupName, clss, rating, name, mount, missile) { export function findHardpoint(groupName, clss, rating, name, mount, missile) {
@@ -158,12 +158,12 @@ export function findHardpoint(groupName, clss, rating, name, mount, missile) {
* Finds a hardpoint module ID based on Class, Rating, Group and/or name. * Finds a hardpoint module ID based on Class, Rating, Group and/or name.
* At least one of Group name or unique module name must be provided * At least one of Group name or unique module name must be provided
* *
* @param {string} groupName [Optional] Full name or abbreviated name for module group * @param {String} groupName [Optional] Full name or abbreviated name for module group
* @param {integer} clss module Class * @param {integer} clss module Class
* @param {string} rating module Rating * @param {String} rating module Rating
* @param {string} name [Optional] Long/unique name for module -e.g. 'Heat Sink Launcher' * @param {String} name [Optional] Long/unique name for module -e.g. 'Heat Sink Launcher'
* @param {string} mount Mount type - [F]ixed, [G]imballed, [T]urret * @param {String} mount Mount type - [F]ixed, [G]imballed, [T]urret
* @param {string} missile [Optional] Missile type - [D]umbfire, [S]eeker * @param {String} missile [Optional] Missile type - [D]umbfire, [S]eeker
* @return {String} The id of the module if found, null if not found * @return {String} The id of the module if found, null if not found
*/ */
export function findHardpointId(groupName, clss, rating, name, mount, missile) { export function findHardpointId(groupName, clss, rating, name, mount, missile) {
@@ -171,24 +171,9 @@ export function findHardpointId(groupName, clss, rating, name, mount, missile) {
return h ? h.id : 0; return h ? h.id : 0;
} }
/**
* Looks up the bulkhead module for a specific ship and bulkhead
* @param {string} shipId Unique ship Id/Key
* @param {string|number} index Index for the specified bulkhead
* @return {Object} The bulkhead module object
*/
export function bulkheads(shipId, index) {
let bulkhead = Ships[shipId].bulkheads[index];
bulkhead.class = 1;
bulkhead.rating = 'I';
bulkhead.name = BulkheadNames[index];
return bulkhead;
}
/** /**
* Get the bulkhead index for the given bulkhead name * Get the bulkhead index for the given bulkhead name
* @param {string} bulkheadName Bulkhead name in english * @param {String} bulkheadName Bulkhead name in english
* @return {number} Bulkhead index * @return {number} Bulkhead index
*/ */
export function bulkheadIndex(bulkheadName) { export function bulkheadIndex(bulkheadName) {
@@ -198,7 +183,7 @@ export function bulkheadIndex(bulkheadName) {
/** /**
* Determine if a module group is a shield generator * Determine if a module group is a shield generator
* @param {string} g Module Group name * @param {String} g Module Group name
* @return {Boolean} True if the group is a shield generator * @return {Boolean} True if the group is a shield generator
*/ */
export function isShieldGenerator(g) { export function isShieldGenerator(g) {
@@ -211,11 +196,9 @@ export function isShieldGenerator(g) {
* *
* 6.5 T is the lightest possible mass of standard components that any ship can use * 6.5 T is the lightest possible mass of standard components that any ship can use
* *
* @param {string} shipId Unique ship Id/Key * @param {String} shipId Unique ship Id/Key
* @return {ModuleSet} The set of modules the ship can install * @return {ModuleSet} The set of modules the ship can install
*/ */
export function forShip(shipId) { export function forShip(shipId) {
let ship = Ships[shipId]; return new ModuleSet(Modules, Ships[shipId]);
let maxInternal = isNaN(ship.slots.internal[0]) ? ship.slots.internal[0].class : ship.slots.internal[0];
return new ModuleSet(Modules, ship.properties.hullMass + 6.5, ship.slots.standard, maxInternal, ship.slots.hardpoints[0]);
} }

View File

@@ -6,6 +6,29 @@ import LZString from 'lz-string';
const STANDARD = ['powerPlant', 'thrusters', 'frameShiftDrive', 'lifeSupport', 'powerDistributor', 'sensors', 'fuelTank']; const STANDARD = ['powerPlant', 'thrusters', 'frameShiftDrive', 'lifeSupport', 'powerDistributor', 'sensors', 'fuelTank'];
/**
* Generates ship-loadout JSON Schema standard object
* @param {Object} standard model
* @return {Object} JSON Schema
*/
function standardToSchema(standard) {
if (standard.m) {
let o = {
class: standard.m.class,
rating: standard.m.rating,
enabled: Boolean(standard.enabled),
priority: standard.priority + 1
};
if (standard.m.name) {
o.name = standard.m.name;
}
return o;
}
return null;
}
/** /**
* Generates ship-loadout JSON Schema slot object * Generates ship-loadout JSON Schema slot object
* @param {Object} slot Slot model * @param {Object} slot Slot model
@@ -59,15 +82,15 @@ export function toDetailedBuild(buildName, ship) {
}], }],
components: { components: {
standard: { standard: {
bulkheads: BulkheadNames[ship.bulkheads.index], bulkheads: BulkheadNames[ship.bulkheads.m.index],
cargoHatch: { enabled: Boolean(ship.cargoHatch.enabled), priority: ship.cargoHatch.priority + 1 }, cargoHatch: { enabled: Boolean(ship.cargoHatch.enabled), priority: ship.cargoHatch.priority + 1 },
powerPlant: { class: standard[0].m.class, rating: standard[0].m.rating, enabled: Boolean(standard[0].enabled), priority: standard[0].priority + 1 }, powerPlant: standardToSchema(standard[0]),
thrusters: { class: standard[1].m.class, rating: standard[1].m.rating, enabled: Boolean(standard[1].enabled), priority: standard[1].priority + 1 }, thrusters: standardToSchema(standard[1]),
frameShiftDrive: { class: standard[2].m.class, rating: standard[2].m.rating, enabled: Boolean(standard[2].enabled), priority: standard[2].priority + 1 }, frameShiftDrive: standardToSchema(standard[2]),
lifeSupport: { class: standard[3].m.class, rating: standard[3].m.rating, enabled: Boolean(standard[3].enabled), priority: standard[3].priority + 1 }, lifeSupport: standardToSchema(standard[3]),
powerDistributor: { class: standard[4].m.class, rating: standard[4].m.rating, enabled: Boolean(standard[4].enabled), priority: standard[4].priority + 1 }, powerDistributor: standardToSchema(standard[4]),
sensors: { class: standard[5].m.class, rating: standard[5].m.rating, enabled: Boolean(standard[5].enabled), priority: standard[5].priority + 1 }, sensors: standardToSchema(standard[5]),
fuelTank: { class: standard[6].m.class, rating: standard[6].m.rating, enabled: Boolean(standard[6].enabled), priority: standard[6].priority + 1 } fuelTank: standardToSchema(standard[6])
}, },
hardpoints: hardpoints.filter(slot => slot.maxClass > 0).map(slotToSchema), hardpoints: hardpoints.filter(slot => slot.maxClass > 0).map(slotToSchema),
utility: hardpoints.filter(slot => slot.maxClass === 0).map(slotToSchema), utility: hardpoints.filter(slot => slot.maxClass === 0).map(slotToSchema),

View File

@@ -3,7 +3,7 @@ import * as Calc from './Calculations';
import * as ModuleUtils from './ModuleUtils'; import * as ModuleUtils from './ModuleUtils';
import LZString from 'lz-string'; import LZString from 'lz-string';
const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs']; const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh'];
/** /**
* Returns the power usage type of a slot and it's particular modul * Returns the power usage type of a slot and it's particular modul
@@ -294,8 +294,8 @@ export default class Ship {
*/ */
getStandardString() { getStandardString() {
if(!this.serialized.standard) { if(!this.serialized.standard) {
this.serialized.standard = this.bulkheads.index + this.standard.reduce((arr, slot, i) => { this.serialized.standard = this.bulkheads.m.index + this.standard.reduce((arr, slot, i) => {
arr[i] = slot.m ? slot.m.class + slot.m.rating : '-'; arr[i] = slot.m ? slot.m.id : '-';
return arr; return arr;
}, new Array(this.standard.length)).join(''); }, new Array(this.standard.length)).join('');
} }
@@ -739,7 +739,7 @@ export default class Ship {
updateTopSpeed() { updateTopSpeed() {
let speeds = Calc.speed(this.unladenMass + this.fuelCapacity, this.speed, this.boost, this.standard[1].m, this.pipSpeed); let speeds = Calc.speed(this.unladenMass + this.fuelCapacity, this.speed, this.boost, this.standard[1].m, this.pipSpeed);
this.topSpeed = speeds['4 Pips']; this.topSpeed = speeds['4 Pips'];
this.topBoost = speeds.boost; this.topBoost = this.canBoost() ? speeds.boost : 0;
return this; return this;
} }
@@ -855,8 +855,7 @@ export default class Ship {
*/ */
useBulkhead(index, preventUpdate) { useBulkhead(index, preventUpdate) {
let oldBulkhead = this.bulkheads.m; let oldBulkhead = this.bulkheads.m;
this.bulkheads.index = index; this.bulkheads.m = this.availCS.getBulkhead(index);
this.bulkheads.m = ModuleUtils.bulkheads(this.id, index);
this.bulkheads.discountedCost = this.bulkheads.m.cost * this.moduleCostMultiplier; this.bulkheads.discountedCost = this.bulkheads.m.cost * this.moduleCostMultiplier;
this.armourMultiplier = ArmourMultiplier[index]; this.armourMultiplier = ArmourMultiplier[index];
this.updateStats(this.bulkheads, this.bulkheads.m, oldBulkhead, preventUpdate); this.updateStats(this.bulkheads, this.bulkheads.m, oldBulkhead, preventUpdate);
@@ -880,7 +879,7 @@ export default class Ship {
/** /**
* Use the lightest standard ModuleUtils unless otherwise specified * Use the lightest standard ModuleUtils unless otherwise specified
* @param {Object} m Module overrides * @param {Object} m Module override set (standard type => module ID)
* @return {this} The ship instance (for chaining operations) * @return {this} The ship instance (for chaining operations)
*/ */
useLightestStandard(m) { useLightestStandard(m) {
@@ -889,16 +888,18 @@ export default class Ship {
let standard = this.standard, let standard = this.standard,
// Find lightest Power Distributor that can still boost; // Find lightest Power Distributor that can still boost;
pd = m.pd ? ModuleUtils.standard(4, m.pd) : this.availCS.lightestPowerDist(this.boostEnergy), pd = m.pd ? ModuleUtils.standard(4, m.pd) : this.availCS.lightestPowerDist(this.boostEnergy),
fsd = m.fsd || standard[2].maxClass + 'A', fsd = ModuleUtils.standard(2, m.fsd || standard[2].maxClass + 'A'),
ls = m.ls || standard[3].maxClass + 'D', ls = ModuleUtils.standard(3, m.ls || standard[3].maxClass + 'D'),
s = m.s || standard[5].maxClass + 'D', s = ModuleUtils.standard(5, m.s || standard[5].maxClass + 'D'),
ft = m.ft ? ModuleUtils.standard(6, m.ft) : standard[6].m, // Use existing fuel tank unless specified
updated; updated;
this.useBulkhead(0) this.useBulkhead(0)
.use(standard[2], ModuleUtils.standard(2, fsd)) // FSD .use(standard[2], fsd) // FSD
.use(standard[3], ModuleUtils.standard(3, ls)) // Life Support .use(standard[3], ls) // Life Support
.use(standard[5], ModuleUtils.standard(5, s)) // Sensors .use(standard[5], s) // Sensors
.use(standard[4], pd); // Power Distributor .use(standard[4], pd) // Power Distributor
.use(standard[6], ft); // Fuel Tank
// Thrusters and Powerplant must be determined after all other ModuleUtils are mounted // Thrusters and Powerplant must be determined after all other ModuleUtils are mounted
// Loop at least once to determine absolute lightest PD and TH // Loop at least once to determine absolute lightest PD and TH

View File

@@ -0,0 +1,126 @@
import * as ModuleUtils from './ModuleUtils';
import { canMount } from '../utils/SlotFunctions';
/**
* Standard / typical role for multi-purpose or combat (if shielded with better bulkheads)
* @param {Ship} ship Ship instance
* @param {Boolean} shielded True if shield generator should be included
* @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames
*/
export function multiPurpose(ship, shielded, bulkheadIndex) {
ship.useStandard('A')
.use(ship.standard[3], ModuleUtils.standard(3, ship.standard[3].maxClass + 'D')) // D Life Support
.use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')) // D Sensors
.useBulkhead(bulkheadIndex);
if (shielded) {
ship.internal.some(function(slot) {
if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield
ship.use(slot, ModuleUtils.findInternal('sg', slot.maxClass, 'A'));
ship.setSlotEnabled(slot, true);
return true;
}
});
}
}
/**
* Trader Role
* @param {Ship} ship Ship instance
* @param {Boolean} shielded True if shield generator should be included
* @param {Object} standardOpts [Optional] Standard module optional overrides
*/
export function trader(ship, shielded, standardOpts) {
let sg = shielded ? ship.getAvailableModules().lightestShieldGenerator(ship.hullMass) : null;
for (let i = ship.internal.length; i--;) {
let slot = ship.internal[i];
if (sg && canMount(ship, slot, 'sg', sg.class)) {
ship.use(slot, sg);
sg = null;
} else {
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
}
}
ship.useLightestStandard(standardOpts);
}
/**
* Explorer Role
* @param {Ship} ship Ship instance
* @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included
*/
export function explorer(ship, planetary) {
let standardOpts = { ppRating: 'A' },
intLength = ship.internal.length,
heatSinkCount = 2, // Fit 2 heat sinks if possible
afmUnitCount = 2, // Fit 2 AFM Units if possible
shieldNext = planetary,
sgSlot,
fuelScoopSlot,
pvhSlot,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
if (!planetary) { // Non-planetary explorers don't really need to boost
standardOpts.pd = '1D';
}
ship.setSlotEnabled(ship.cargoHatch, false)
.use(ship.internal[--intLength], ModuleUtils.internal('2f')); // Advanced Discovery Scanner
if (!planetary || intLength > 3) { // Don't mount a DDS on planetary explorer ships too small for both a PVH and DDS
ship.use(ship.internal[--intLength], ModuleUtils.internal('2i')); // Detailed Surface Scanner
}
for (let i = 0; i < intLength; i++) {
let slot = ship.internal[i];
let nextSlot = (i + 1) < intLength ? ship.internal[i + 1] : null;
// Fit best possible Fuel Scoop
if (!fuelScoopSlot && canMount(ship, slot, 'fs')) {
fuelScoopSlot = slot;
ship.use(slot, ModuleUtils.findInternal('fs', slot.maxClass, 'A'));
ship.setSlotEnabled(slot, true);
// Mount a Shield generator if possible AND an AFM Unit has been mounted already (Guarantees at least 1 AFM Unit)
} else if (!sgSlot && shieldNext && canMount(ship, slot, 'sg', sg.class) && !canMount(ship, nextSlot, 'sg', sg.class)) {
sgSlot = slot;
shieldNext = false;
ship.use(slot, sg);
ship.setSlotEnabled(slot, true);
// if planetary explorer and the next slot cannot mount a PVH or the next modul to mount is a SG
} else if (planetary && !pvhSlot && canMount(ship, slot, 'pv') && (shieldNext || !canMount(ship, nextSlot, 'pv', 2))) {
pvhSlot = slot;
ship.use(slot, ModuleUtils.findInternal('pv', Math.min(Math.floor(pvhSlot.maxClass / 2) * 2, 6), 'G'));
ship.setSlotEnabled(slot, false); // Disabled power for PVH
shieldNext = !sgSlot;
} else if (afmUnitCount > 0 && canMount(ship, slot, 'am')) {
afmUnitCount--;
ship.use(slot, ModuleUtils.findInternal('am', slot.maxClass, 'A'));
ship.setSlotEnabled(slot, false); // Disabled power for AFM Unit
shieldNext = !sgSlot;
} else {
ship.use(slot, null);
}
}
for (let s of ship.hardpoints) {
if (s.maxClass == 0 && heatSinkCount) { // Mount up to 2 heatsinks
ship.use(s, ModuleUtils.hardpoints('02'));
ship.setSlotEnabled(s, heatSinkCount == 2); // Only enable a single Heatsink
heatSinkCount--;
} else {
ship.use(s, null);
}
}
if (sgSlot) {
// The SG and Fuel scoop to not need to be powered at the same time
if (sgSlot.m.power > fuelScoopSlot.m.power) { // The Shield generator uses the most power
ship.setSlotEnabled(fuelScoopSlot, false);
} else { // The Fuel scoop uses the most power
ship.setSlotEnabled(sgSlot, false);
}
}
ship.useLightestStandard(standardOpts);
}

View File

@@ -2,6 +2,26 @@ import React from 'react';
import cn from 'classnames'; import cn from 'classnames';
import { isShieldGenerator } from '../shipyard/ModuleUtils'; import { isShieldGenerator } from '../shipyard/ModuleUtils';
import { Infinite } from '../components/SvgIcons'; import { Infinite } from '../components/SvgIcons';
import Persist from '../stores/Persist';
/**
* Determine if a slot on a ship can mount a module of a particular class and group
* @param {Object} ship Ship object
* @param {Object} slot Slot object
* @param {String} group Module group/type abbrivation/code
* @param {Integer} clazz [Optional] Module Class/Size
* @return {Boolean} True if the slot can mount the module
*/
export function canMount(ship, slot, group, clazz) {
if (slot &&
(!slot.eligible || slot.eligible[group]) &&
(group != 'pcq' || (ship.luxuryCabins && ship.luxuryCabins === true)) &&
(group != 'fh' || (ship.fighterHangars && ship.fighterHangars === true)) &&
(clazz === undefined || slot.maxClass >= clazz)) {
return true;
}
return false;
}
/** /**
* Returns the translate name for the module mounted in the specified * Returns the translate name for the module mounted in the specified
@@ -76,7 +96,9 @@ const PROP_BLACKLIST = {
eddbID: 1, eddbID: 1,
edID: 1, edID: 1,
id: 1, id: 1,
index: 1,
'class': 1, 'class': 1,
rating: 1,
maxfuel: 1, maxfuel: 1,
fuelmul: 1, fuelmul: 1,
fuelpower: 1, fuelpower: 1,
@@ -105,7 +127,11 @@ const PROP_BLACKLIST = {
enginecapacity: 1, enginecapacity: 1,
enginerecharge: 1, enginerecharge: 1,
systemcapacity: 1, systemcapacity: 1,
systemrecharge: 1 systemrecharge: 1,
breachdps: 1,
breachmin: 1,
breachmax: 1,
integrity: 1
}; };
const TERM_LOOKUP = { const TERM_LOOKUP = {
@@ -158,10 +184,10 @@ function diffClass(a, b, negative) {
*/ */
function diff(format, mVal, mmVal) { function diff(format, mVal, mmVal) {
if (mVal == Infinity) { if (mVal == Infinity) {
return <Infinite/>; return '∞';
} else { } else {
let diff = mVal - mmVal; let diff = mVal - mmVal;
if (!diff || !mVal || diff == mVal || Math.abs(diff) == Infinity) { if (!diff || mVal === undefined || diff == mVal || Math.abs(diff) == Infinity) {
return format(mVal); return format(mVal);
} }
return `${format(mVal)} (${diff > 0 ? '+' : ''}${format(diff)})`; return `${format(mVal)} (${diff > 0 ? '+' : ''}${format(diff)})`;
@@ -190,7 +216,7 @@ export function diffDetails(language, m, mm) {
let mAffectsShield = isShieldGenerator(m.grp) || m.grp == 'sb'; let mAffectsShield = isShieldGenerator(m.grp) || m.grp == 'sb';
let mmAffectsShield = isShieldGenerator(mm.grp) || mm.grp == 'sb'; let mmAffectsShield = isShieldGenerator(mm.grp) || mm.grp == 'sb';
propDiffs.push(<div key='cost'>{translate('cost')}: <span className={diffClass(m.cost, mm.cost, true) }>{formats.int(m.cost || 0)}{units.CR}</span></div>); propDiffs.push(<div key='cost'>{translate('cost')}: <span className={diffClass(m.cost, mm.cost, true) }>{m.cost ? Math.round(m.cost * (1 - Persist.getModuleDiscount())) : 0}{units.CR}</span></div>);
propDiffs.push(<div key='mass'>{translate('mass')}: <span className={diffClass(mMass, mm.mass, true)}>{diff(formats.round, mMass, mmMass)}{units.T}</span></div>); propDiffs.push(<div key='mass'>{translate('mass')}: <span className={diffClass(mMass, mm.mass, true)}>{diff(formats.round, mMass, mmMass)}{units.T}</span></div>);
for (let p in m) { for (let p in m) {
@@ -218,7 +244,7 @@ export function diffDetails(language, m, mm) {
} }
let sgDiffClass = Math.round((newShield - shield) * 100) / 100 == 0 ? 'muted' : (newShield > shield ? 'secondary' : 'warning'); let sgDiffClass = Math.round((newShield - shield) * 100) / 100 == 0 ? 'muted' : (newShield > shield ? 'secondary' : 'warning');
propDiffs.push(<div key='shields'>{`${translate('shields')}: `}<span className={sgDiffClass}>{diff(formats.int, newShield, shield)}{units.MJ}</span></div>); propDiffs.push(<div key='shields'>{translate('shields')}: <span className={sgDiffClass}>{diff(formats.int, newShield, shield)}{units.MJ}</span></div>);
} }
if (m.grp == 'pd') { if (m.grp == 'pd') {
@@ -248,10 +274,10 @@ export function diffDetails(language, m, mm) {
let ladenRange = this.calcLadenRange(massDiff + capDiff, m.fuel, fsd); let ladenRange = this.calcLadenRange(massDiff + capDiff, m.fuel, fsd);
if (maxRange != this.unladenRange) { if (maxRange != this.unladenRange) {
propDiffs.push(<div key='maxRange'>{`${translate('max')} ${translate('jump range')}: `}<span className={maxRange > this.unladenRange ? 'secondary' : 'warning'}>{formats.round(maxRange)}{units.LY}</span></div>); propDiffs.push(<div key='maxRange'>{translate('max')} {translate('jump range')}: <span className={maxRange > this.unladenRange ? 'secondary' : 'warning'}>{formats.round(maxRange)}{units.LY}</span></div>);
} }
if (ladenRange != this.ladenRange) { if (ladenRange != this.ladenRange) {
propDiffs.push(<div key='unladenRange'>{`${translate('laden')} ${translate('jump range')}: `}<span className={ladenRange > this.ladenRange ? 'secondary' : 'warning'}>{formats.round(ladenRange)}{units.LY}</span></div>); propDiffs.push(<div key='unladenRange'>{translate('laden')} {translate('jump range')}: <span className={ladenRange > this.ladenRange ? 'secondary' : 'warning'}>{formats.round(ladenRange)}{units.LY}</span></div>);
} }
} }

14
src/index.html Executable file → Normal file
View File

@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html {%= o.htmlWebpackPlugin.options.appCache ? 'manifest=/' + o.htmlWebpackPlugin.options.appCache : '' %} > <html {%= o.htmlWebpackPlugin.options.appCache ? 'manifest=/' + o.htmlWebpackPlugin.options.appCache : '' %} >
<head> <head>
<title>Coriolis</title> <title>Coriolis EDCD Edition</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="stylesheet" href="{%= o.htmlWebpackPlugin.files.css[0] %}"> <link rel="stylesheet" href="{%= o.htmlWebpackPlugin.files.css[0] %}">
<!-- Standard headers --> <!-- Standard headers -->
@@ -25,12 +25,11 @@
</head> </head>
<body style="background-color:#000;"> <body style="background-color:#000;">
<section id="coriolis"></section> <section id="coriolis"></section>
<footer> <script>
<div class="right cap"> window.CORIOLIS_GAPI_KEY = '{%= o.htmlWebpackPlugin.options.gapiKey %}';
<a href="https://github.com/cmmcleod/coriolis/releases/" target="_blank" title="Coriolis Github Project">{%= o.htmlWebpackPlugin.options.version %} - {%= new Date().toISOString().slice(0, 10) %}</a> window.CORIOLIS_VERSION = '{%= o.htmlWebpackPlugin.options.version %}';
</div> window.CORIOLIS_DATE = '{%= new Date().toISOString().slice(0, 10) %}';
</footer> </script>
<script src="{%= o.htmlWebpackPlugin.files.chunks.lib.entry %}" charset="utf-8" crossorigin="anonymous"></script> <script src="{%= o.htmlWebpackPlugin.files.chunks.lib.entry %}" charset="utf-8" crossorigin="anonymous"></script>
<script src="{%= o.htmlWebpackPlugin.files.chunks.app.entry %}" charset="utf-8" crossorigin="anonymous"></script> <script src="{%= o.htmlWebpackPlugin.files.chunks.app.entry %}" charset="utf-8" crossorigin="anonymous"></script>
<script> <script>
@@ -41,7 +40,6 @@
})(window,document,'script','//www.google-analytics.com/analytics.js','ga'); })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', '{%= o.htmlWebpackPlugin.options.uaTracking %}', 'auto'); ga('create', '{%= o.htmlWebpackPlugin.options.uaTracking %}', 'auto');
{% } %} {% } %}
window.CORIOLIS_GAPI_KEY = '{%= o.htmlWebpackPlugin.options.gapiKey %}';
</script> </script>
</body> </body>
</html> </html>

View File

@@ -23,9 +23,7 @@ html, body {
height: 100%; height: 100%;
width: 100%; width: 100%;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
overflow-x: hidden; overflow: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
} }
body { body {
@@ -42,17 +40,23 @@ div, a, li {
#coriolis { #coriolis {
width: 100%; width: 100%;
min-height: 95%; height: 100%;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
} }
.page { .page {
margin: 0; margin: 0;
padding: 0.5em 0; padding: 0.5em;
width: 100%; width: 100%;
min-height: 100%; min-height: 100%;
clear: both; clear: both;
text-align: center; text-align: center;
box-sizing: border-box; box-sizing: border-box;
.largePhone({
padding: 0.5em 0.25em;
});
} }
.l { .l {

View File

@@ -97,6 +97,7 @@ header {
input { input {
border: none; border: none;
background-color: transparent;
text-align: right; text-align: right;
font-size: 1em; font-size: 1em;
font-family: @fStandard; font-family: @fStandard;
@@ -171,7 +172,7 @@ header {
&:visited { &:visited {
color: @warning; color: @warning;
} }
&:hover { .no-touch &:hover {
color: teal; color: teal;
} }
&.active { &.active {

View File

@@ -78,7 +78,7 @@ select {
stroke-width: 0.5em; stroke-width: 0.5em;
stroke: @primary-disabled; stroke: @primary-disabled;
&:hover { .no-touch &:hover {
border-color: @primary; border-color: @primary;
color: @primary; color: @primary;
stroke: @primary; stroke: @primary;
@@ -95,14 +95,14 @@ select {
color: @warning-disabled; color: @warning-disabled;
stroke: @warning-disabled; stroke: @warning-disabled;
&:hover { .no-touch &:hover {
border-color: @warning; border-color: @warning;
color: @warning; color: @warning;
stroke: @warning; stroke: @warning;
} }
} }
&.disabled { &.disabled, &.disabled:hover {
cursor: not-allowed; cursor: not-allowed;
border-color: @disabled; border-color: @disabled;
color: @disabled; color: @disabled;
@@ -150,4 +150,10 @@ select {
} }
} }
&.standard {
ul {
width: 16.25em;
}
}
} }

View File

@@ -12,6 +12,8 @@
background-color: @primary-bg; background-color: @primary-bg;
border: 1px solid @primary-disabled; border: 1px solid @primary-disabled;
color: @fg; color: @fg;
stroke: @fg;
stroke-width: 20;
fill: @fg; fill: @fg;
.details-container { .details-container {
@@ -54,6 +56,8 @@
font-size: 1.2em; font-size: 1.2em;
width: 1.2em; width: 1.2em;
color: @primary-disabled; color: @primary-disabled;
stroke: @primary-disabled;
stroke-width: 20;
border-right: 1px solid @primary-disabled; border-right: 1px solid @primary-disabled;
box-sizing: border-box; box-sizing: border-box;
padding-top: 0.2em; padding-top: 0.2em;
@@ -64,6 +68,8 @@
text-transform: uppercase; text-transform: uppercase;
font-size: 1.3em; font-size: 1.3em;
color: lighten(@primary-bg, 12%); color: lighten(@primary-bg, 12%);
stroke: lighten(@primary-bg, 12%);
stroke-width: 20;
text-align: center; text-align: center;
letter-spacing: 0.1em; letter-spacing: 0.1em;
line-height: 1.7em; line-height: 1.7em;
@@ -71,12 +77,16 @@
&.selected { &.selected {
color: @primary-bg; color: @primary-bg;
stroke: @primary-bg;
stroke-width: 20;
fill: @primary-bg; fill: @primary-bg;
background-color: @primary; background-color: @primary;
border: 1px solid @primary; border: 1px solid @primary;
z-index: 1; z-index: 1;
.sz { .sz {
color: @primary; color: @primary;
stroke: @primary;
stroke-width: 20;
background-color: @primary-bg; background-color: @primary-bg;
border-right: 1px solid @primary; border-right: 1px solid @primary;
} }

View File

@@ -2,7 +2,7 @@
.user-select-none(); .user-select-none();
cursor: pointer; cursor: pointer;
&:hover { .no-touch &:hover {
color: @primary; color: @primary;
} }
} }

View File

@@ -33,6 +33,10 @@ thead {
&.lft { &.lft {
border-left: 1px solid @primary-bg; border-left: 1px solid @primary-bg;
} }
&.rgt {
border-right: 1px solid @primary-bg;
}
} }
} }
@@ -43,23 +47,26 @@ tbody tr {
text-align: right; text-align: right;
} }
td { .no-touch &.highlight:hover, .no-touch &.highlighted {
line-height: 1.4em; background-color: @warning-bg;
padding: 0 0.3em;
&.val {
border: 1px solid @primary-disabled;
}
&.lbl {
border: 1px solid @primary-disabled;
text-transform: uppercase;
color: @primary-bg;
background-color: @primary-disabled;
}
} }
} }
td { td {
line-height: 1.4em;
padding: 0 0.3em;
&.val {
border: 1px solid @primary-disabled;
}
&.lbl {
border: 1px solid @primary-disabled;
text-transform: uppercase;
color: @primary-bg;
background-color: @primary-disabled;
}
&.tl { &.tl {
text-align: left; text-align: left;
padding-left: 0.7em; padding-left: 0.7em;

79
src/migrate.html Normal file
View File

@@ -0,0 +1,79 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Coriolis: Migrate to HTTPS</title>
</head>
<body>
<script>
function fromJsonToObject(str) {
try {
var o = JSON.parse(str);
if (o instanceof Object && !(o instanceof Array)) {
return o;
}
} catch (e) { }
return {};
}
function listener(event) {
var origin = event.origin || event.originalEvent.origin;
var source = event.source;
var data = event.message;
if (window.location.href.indexOf('coriolis') != -1 && origin !== "http://coriolis.io") {
return;
}
try {
var builds = fromJsonToObject(localStorage.getItem('builds'));
var comparisons = fromJsonToObject(localStorage.getItem('comparisons'));
// merge builds
if (typeof data.builds == 'object') {
for (var bName in data.builds) {
// Build existing in http and does not existing in HTTPS
if (data.builds.hasOwnProperty(bName) && !builds[bName]) {
build[bName] = data.builds[bName];
}
}
localStorage.setItem('builds', JSON.stringify(builds));
}
// merge comparisons
if (typeof data.comparisons == 'object') {
for (var comp in data.comparisons) {
// Comparison existing in http and does not existing in HTTPS
if (data.comparisons.hasOwnProperty(comp) && !data.comparisons[comp]) {
comparisons[comp] = data.comparisons[comp];
}
}
localStorage.setItem('comparisons', JSON.stringify(comparisons));
}
source.postMessage({
success: true,
buildCount: Object.keys(builds).length,
comparisonCount: Object.keys(comparisons).length
}, origin);
} catch (e) {
source.postMessage({ success: false, error: e }, origin);
}
}
if (window.addEventListener){
window.addEventListener("message", listener, false);
} else {
window.attachEvent("onmessage", listener);
}
</script>
</body>
</html>

View File

@@ -76,7 +76,11 @@
"class": { "type": "integer", "minimum": 2, "maximum": 8 }, "class": { "type": "integer", "minimum": 2, "maximum": 8 },
"rating": { "$ref": "#/definitions/standardRatings" }, "rating": { "$ref": "#/definitions/standardRatings" },
"enabled": { "type": "boolean" }, "enabled": { "type": "boolean" },
"priority": { "type": "integer", "minimum": 1, "maximum": 5 } "priority": { "type": "integer", "minimum": 1, "maximum": 5 },
"name": {
"description": "The name identifing the thrusters (if applicable), e.g. 'Enhanced Performance'",
"type": "string"
}
} }
}, },
"frameShiftDrive": { "frameShiftDrive": {

View File

@@ -1,16 +1,28 @@
var path = require('path'); var path = require('path');
var exec = require('child_process').exec;
var webpack = require('webpack'); var webpack = require('webpack');
var pkgJson = require('./package'); var pkgJson = require('./package');
var HtmlWebpackPlugin = require("html-webpack-plugin"); var HtmlWebpackPlugin = require("html-webpack-plugin");
var ExtractTextPlugin = require("extract-text-webpack-plugin"); var ExtractTextPlugin = require("extract-text-webpack-plugin");
function CopyDirPlugin(source, destination) {
this.source = source;
this.destination = destination;
}
CopyDirPlugin.prototype.apply = function(compiler) {
compiler.plugin('done', function() {
console.log(compiler.outputPath, this.destination);
exec('cp -r ' + this.source + ' ' + path.join(compiler.outputPath, this.destination));
}.bind(this));
};
module.exports = { module.exports = {
devtool: 'eval', devtool: 'eval',
devServer: { devServer: {
headers: { "Access-Control-Allow-Origin": "*" } headers: { "Access-Control-Allow-Origin": "*" }
}, },
entry: { entry: {
app: [ 'webpack-dev-server/client?http://localhost:3300', 'webpack/hot/only-dev-server', path.join(__dirname, "src/app/index.js") ], app: [ 'webpack-dev-server/client?http://0.0.0.0:3300', 'webpack/hot/only-dev-server', path.join(__dirname, "src/app/index.js") ],
lib: ['d3', 'react', 'react-dom', 'classnames', 'fbemitter', 'lz-string'] lib: ['d3', 'react', 'react-dom', 'classnames', 'fbemitter', 'lz-string']
}, },
resolve: { resolve: {
@@ -23,6 +35,7 @@ module.exports = {
publicPath: '/' publicPath: '/'
}, },
plugins: [ plugins: [
new CopyDirPlugin(path.join(__dirname, 'src/.htaccess'), ''),
new webpack.optimize.CommonsChunkPlugin('lib', 'lib.js'), new webpack.optimize.CommonsChunkPlugin('lib', 'lib.js'),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
inject: false, inject: false,

View File

@@ -74,6 +74,8 @@ module.exports = {
}), }),
new CopyDirPlugin(path.join(__dirname, 'src/schemas'), 'schemas'), new CopyDirPlugin(path.join(__dirname, 'src/schemas'), 'schemas'),
new CopyDirPlugin(path.join(__dirname, 'src/images/logo/*'), ''), new CopyDirPlugin(path.join(__dirname, 'src/images/logo/*'), ''),
new CopyDirPlugin(path.join(__dirname, 'src/migrate.html'), ''),
new CopyDirPlugin(path.join(__dirname, 'src/.htaccess'), ''),
new AppCachePlugin({ new AppCachePlugin({
network: ['*'], network: ['*'],
settings: ['prefer-online'], settings: ['prefer-online'],