Compare commits

..

152 Commits

Author SHA1 Message Date
willyb321
250d79162b fix crashing on engineering and prod webpack 2019-04-01 07:14:38 +11:00
willyb321
9fc7e64499 final tweaks 2019-04-01 04:38:43 +11:00
willyb321
6223908020 add more clippy quotes 2019-03-31 09:43:52 +11:00
willyb321
c73dd71953 owoify text 2019-03-29 08:53:16 +11:00
willyb321
c7062de5e6 pink coriolis 2019-03-29 08:53:11 +11:00
willyb321
82132d9141 clippy for april 2019-03-29 08:53:03 +11:00
Mingming Cui
435c1b6d45 Chinese translation (#481)
* Chinese translation

* Added Chinese translation for UI

* Made some strings translatable.

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

* fixed en format

* Fixed one capitalization mistake

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

* Add missing argument when calculating fullTankRange

* Use opponent PD when calculating how long shields will hold

Closes #430

* Allow modifying max mass ONLY for shield generators

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

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

* Fix/no adsense (#412)

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

* remove script tag
2018-10-24 09:06:24 +11:00
William
b59fa15e00 Merge pull request #411 from EDCD/fix/chart-labels
Fix VerticalBarchart labels
2018-10-24 07:52:58 +11:00
felixlinker
12bca4c44e Fix VerticalBarchart labels 2018-10-23 21:48:07 +01:00
William Blythe
2858ef3e93 Merge branch 'develop' 2018-10-23 13:03:58 +11:00
William Blythe
7d99471f89 sw 2018-10-23 12:41:56 +11:00
William Blythe
a2ab708ac9 format 2018-10-23 12:14:28 +11:00
William Blythe
a34a9c355f fix sw not registered 2018-10-23 11:57:07 +11:00
William Blythe
3215b3942d fix sw not registered 2018-10-23 11:56:33 +11:00
William Blythe
557c0afd9b Merge branch 'master' into develop 2018-10-23 11:50:58 +11:00
William Blythe
d52365a204 /shrug 2018-10-23 09:55:42 +11:00
felixlinker
14b2a14e58 Merge branch 'master' into develop 2018-10-22 23:52:09 +01:00
William Blythe
7f24904f77 various bits 2018-10-23 09:46:50 +11:00
William Blythe
da07790594 various bits 2018-10-23 09:40:13 +11:00
William
5008c7cd74 Update sw.js 2018-10-23 08:15:54 +11:00
willyb321
a778b1b6e1 update 2018-10-22 17:15:09 +11:00
William Blythe
bd9771f9ba test run 2018-10-22 11:49:15 +11:00
William Blythe
600c244f9b test run 2018-10-22 11:44:14 +11:00
William Blythe
a599b1a076 remove d3 rollup that doesnt seem to serve a purpose 2018-10-22 09:21:33 +11:00
willyb321
3e0a5e22b1 Merge branch 'develop' 2018-10-21 10:21:04 +11:00
willyb321
3a6ac818c2 reload anyway 2018-10-21 10:17:53 +11:00
willyb321
6f077d4c41 use register-service-worker 2018-10-21 10:16:31 +11:00
willyb321
9c767c928c fix icons and manifest
was not flattened
2018-10-21 10:03:01 +11:00
willyb321
515f4ad3da sw work 2018-10-21 09:50:15 +11:00
willyb321
4fcf074595 dont git reset in docker 2018-10-21 08:37:08 +11:00
willyb321
e5f8153a34 add announcements to menu 2018-10-21 08:27:53 +11:00
William
571854a11c Merge pull request #405 from EDCD/feature/code-quality
Cosmetical improvements
2018-10-20 11:00:58 +11:00
William
1f22f249a1 Merge pull request #407 from EDCD/feature/number-inputs
Enhance number editing
2018-10-20 11:00:47 +11:00
felixlinker
718ac0a514 Enhance number editing 2018-10-13 22:29:12 +01:00
willyb321
8f089cb1ee hopefully fix some sw bugs 2018-10-13 09:22:16 +11:00
felixlinker
d19a7276dd Optimized imports 2018-10-05 00:06:57 +01:00
felixlinker
10fffe67fc Code style fixes 2018-10-05 00:06:33 +01:00
William Blythe
f0bf8e8ce2 import babel polyfill 2018-10-03 14:17:56 +10:00
William
598cf8d677 Merge pull request #404 from EDCD/feature/fix-imports
Fix journal import
2018-10-02 10:14:02 +10:00
felixlinker
90f03de3fe Fix journal import 2018-10-01 22:24:18 +01:00
William
e0766f4424 Merge pull request #400 from EDCD/feature/fixes
Various fixes
2018-09-30 06:21:06 +10:00
felixlinker
28a90768e4 Start searching for available slots at 0 because the Type9 starts there 2018-09-29 20:26:40 +01:00
felixlinker
f3d917ccbe Fixed trailing zero check in journal style import 2018-09-29 20:04:40 +01:00
felixlinker
7e5d52385d Fixed loops in journal style import 2018-09-29 20:03:58 +01:00
felixlinker
4368015dc0 Fixed variable assignement that should be a comparison 2018-09-29 17:45:45 +01:00
felixlinker
1201da1811 Differentiate between uneditable and un-highlighted properties in the modifications menu 2018-09-29 16:40:36 +01:00
felixlinker
d195b568b0 eslint indentation fixes 2018-09-29 01:03:00 +01:00
felixlinker
c9866c146b Stop dividing burst two times by 100 2018-09-29 00:55:37 +01:00
felixlinker
5d52809d0d Catch undefined values in ship build parsing 2018-09-28 23:28:46 +01:00
willyb321
8f0cca4fd9 fix 2018-09-29 08:06:15 +10:00
willyb321
e46bb425fe minimize in prod 2018-09-29 07:50:36 +10:00
willyb321
06dc110025 migrate to babel 7, webpack 4, dep updates, lots 2018-09-29 07:49:04 +10:00
felixlinker
e9c34c636a Don't filter values but map falsy ones to zero 2018-09-28 22:35:07 +01:00
felixlinker
59d38cbd33 use react-extras instead of auto-bind 2018-09-26 23:15:09 +01:00
William
51f5188efc Merge pull request #393 from EDCD/feature/mc-pips
Add multicrew pips
2018-09-25 07:15:44 +10:00
felixlinker
be8934da80 Added multi crew pips 2018-09-23 22:30:05 +01:00
felixlinker
18d78b3089 Added auto-bind dependency 2018-09-23 22:29:45 +01:00
willyb321
b1ff4e84f7 Merge branch 'master' into develop 2018-09-23 11:42:45 +10:00
willyb321
bed2ede701 Merge branch 'release/3.0.0' 2018-09-23 11:42:44 +10:00
willyb321
124bd62d2c 3.0.0 2018-09-23 11:42:38 +10:00
willyb321
975846f4ab Merge branch 'master' into develop 2018-09-23 11:41:23 +10:00
willyb321
3f73f9be10 Merge branch 'release/2.9.18' 2018-09-23 11:41:22 +10:00
94 changed files with 8795 additions and 8334 deletions

View File

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

View File

@@ -1,2 +0,0 @@
node_modules
npm-debug.log

View File

@@ -1,45 +0,0 @@
worker_processes 1;
user nobody nobody;
error_log /tmp/error.log;
pid /tmp/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
client_body_temp_path /tmp/client_body;
fastcgi_temp_path /tmp/fastcgi_temp;
proxy_temp_path /tmp/proxy_temp;
scgi_temp_path /tmp/scgi_temp;
uwsgi_temp_path /tmp/uwsgi_temp;
access_log /tmp/access.log;
error_log /tmp/error.log;
keepalive_timeout 3000;
server {
listen 80;
listen [::]:80;
index index.html;
server_name localhost;
root /usr/share/nginx/html;
autoindex on;
location ~* \.(?:manifest|appcache|html?|xml|json|css|js|map|jpg|jpeg|gif|png|ico|svg|eot|ttf|woff|woff2)$ {
expires -1;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
access_log off;
}
location / {
try_files $uri $uri/ /index.html =404;
}
}
}

77
.dockerignore Normal file
View File

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

View File

@@ -35,7 +35,7 @@
"title": "Coriolis",
"description": "Coriolis Shipyard for Elite Dangerous",
"repository": "https://github.com/EDCD/coriolis",
"site": "https://coriolis.edcd.io",
"site": "https://coriolis.io",
"author": "https://github.com/edcd",
"image": "./src/images/logo/192x192.png"
}
@@ -81,7 +81,7 @@
"title": "Coriolis",
"description": "Coriolis Shipyard for Elite Dangerous",
"repository": "https://github.com/EDCD/coriolis",
"site": "https://coriolis.edcd.io",
"site": "https://coriolis.io",
"author": "https://github.com/edcd",
"image": "./src/images/logo/192x192.png"
}
@@ -100,4 +100,4 @@
}
}
]
}
}

13
.gitlab-ci.yml Normal file
View File

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

View File

@@ -6,31 +6,28 @@ WORKDIR /src/app
RUN mkdir -p /src/app/coriolis
RUN mkdir -p /src/app/coriolis-data
COPY ./coriolis/ /src/app/coriolis
COPY ./coriolis-data/ /src/app/coriolis-data
RUN apk add --update git
RUN apk update
RUN apk add git
COPY . /src/app/coriolis
RUN npm i -g npm
# Set up coriolis-data
WORKDIR /src/app/coriolis-data
RUN git fetch --all
RUN git reset --hard origin/$BRANCH
RUN git clone https://github.com/EDCD/coriolis-data.git .
RUN git checkout ${BRANCH}
RUN npm install --no-package-lock
RUN npm start
# Set up coriolis
WORKDIR /src/app/coriolis
RUN git fetch --all
RUN git reset --hard origin/$BRANCH
RUN npm install --no-package-lock
RUN npm run build
### STAGE 2: Production Environment ###
FROM nginx:1.13.12-alpine as web
COPY coriolis/.docker/nginx.conf /etc/nginx/nginx.conf
FROM fholzer/nginx-brotli as web
COPY nginx.conf /etc/nginx/nginx.conf
COPY --from=builder /src/app/coriolis/build /usr/share/nginx/html
WORKDIR /usr/share/nginx/html
EXPOSE 80

24
LICENSE.md Normal file
View File

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

11
d3-funcs.js vendored
View File

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

12659
d3.js vendored

File diff suppressed because it is too large Load Diff

4
d3.min.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -10,6 +10,8 @@ services:
- web
labels:
- "traefik.docker.network=web"
- "traefik.frontend.redirect.regex=^https:\/\/coriolis\.edcd\.io\/(.*)"
- "traefik.frontend.redirect.replacement=https://coriolis.io/$1"
- "traefik.enable=true"
- "traefik.basic.frontend.rule=Host:coriolis.io,coriolis.edcd.io"
- "traefik.basic.port=80"
@@ -25,10 +27,26 @@ services:
labels:
- "traefik.docker.network=web"
- "traefik.enable=true"
- "traefik.frontend.redirect.regex=^https:\/\/beta\.coriolis\.edcd\.io\/(.*)"
- "traefik.frontend.redirect.replacement=https://beta.coriolis.io/$1"
- "traefik.basic.frontend.rule=Host:beta.coriolis.io,beta.coriolis.edcd.io"
- "traefik.basic.port=80"
- "traefik.basic.protocol=http"
coriolis_dw2:
image: edcd/coriolis:dw2
restart: always
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
networks:
- web
labels:
- "traefik.docker.network=web"
- "traefik.enable=true"
- "traefik.basic.frontend.rule=Host:dw2.coriolis.io"
- "traefik.basic.port=80"
- "traefik.basic.protocol=http"
networks:
web:
external: true

View File

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

View File

@@ -1,125 +1,146 @@
{
"name": "coriolis_shipyard",
"version": "2.9.18",
"repository": {
"type": "git",
"url": "https://github.com/EDCD/coriolis"
},
"homepage": "https://coriolis.edcd.io",
"bugs": "https://github.com/EDCD/coriolis/issues",
"private": true,
"engine": "node >= 4.8.1",
"license": "MIT",
"scripts": {
"prepublish": "rollup -c && uglifyjs d3.js -c -m -o d3.min.js",
"extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f",
"clean": "rimraf build",
"start": "node devServer.js",
"lint": "eslint --ext .js,.jsx src",
"test": "jest",
"prod-serve": "nginx -p $(pwd) -c nginx.conf",
"prod-stop": "kill -QUIT $(cat nginx.pid)",
"build": "npm run clean && cross-env NODE_ENV=production webpack -p --config webpack.config.prod.js",
"rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws",
"deploy": "npm run lint && npm test && npm run build && npm run rsync"
},
"jest": {
"transform": {
".*": "<rootDir>/node_modules/babel-jest"
},
"testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$",
"moduleFileExtensions": [
"js",
"json",
"jsx"
],
"automock": true,
"bail": false,
"unmockedModulePathPatterns": [
"<rootDir>/node_modules/lodash",
"<rootDir>/node_modules/react",
"<rootDir>/node_modules/react-dom",
"<rootDir>/node_modules/react-transition-group",
"<rootDir>/node_modules/react-testutils-additions",
"<rootDir>/node_modules/fbjs",
"<rootDir>/node_modules/fbemitter",
"<rootDir>/node_modules/classnames",
"<rootDir>/node_modules/d3",
"<rootDir>/node_modules/lz-string",
"<rootDir>/node_modules/jsen",
"coriolis-data",
"<rootDir>/src/app/shipyard",
"<rootDir>/src/app/i18n",
"<rootDir>/src/app/utils",
"<rootDir>/src/schemas",
"<rootDir>/__tests__"
]
},
"devDependencies": {
"appcache-webpack-plugin": "^1.3.0",
"babel-core": "*",
"babel-eslint": "*",
"babel-jest": "*",
"babel-loader": "*",
"babel-preset-env": "*",
"babel-preset-react": "*",
"babel-preset-stage-0": "*",
"create-react-class": "^15.6.2",
"cross-env": "^5.1.4",
"css-loader": "^0.28.0",
"d3-selection": "1",
"esdoc": "^1.1.0",
"esdoc-custom-theme": "^1.4.2",
"esdoc-ecmascript-proposal-plugin": "^1.0.0",
"esdoc-jsx-plugin": "^1.0.0",
"esdoc-publish-html-plugin": "^1.1.2",
"esdoc-react-plugin": "^1.0.1",
"esdoc-standard-plugin": "^1.0.0",
"eslint": "3.19.0",
"eslint-plugin-react": "^6.10.3",
"expose-loader": "^0.7.3",
"express": "^4.15.2",
"extract-text-webpack-plugin": "2.1.0",
"file-loader": "^0.11.1",
"html-webpack-plugin": "^2.28.0",
"jest-cli": "^21.2.1",
"jsen": "^0.6.4",
"json-loader": "^0.5.4",
"less": "^2.7.2",
"less-loader": "^4.0.3",
"react-addons-perf": "^15.4.2",
"react-container-dimensions": "^1.4.1",
"react-testutils-additions": "^15.2.0",
"react-transition-group": "^1.1.2",
"rimraf": "^2.6.1",
"rollup": "0.41",
"rollup-plugin-node-resolve": "3",
"style-loader": "^0.16.1",
"uglify-js": "^2.4.11",
"url-loader": "^0.5.8",
"webpack": "^2.4.1",
"webpack-bugsnag-plugins": "^1.1.1",
"webpack-dev-server": "^2.4.4",
"webpack-notifier": "^1.6.0",
"workbox-webpack-plugin": "^3.4.1"
},
"dependencies": {
"babel-polyfill": "*",
"browserify-zlib-next": "^1.0.1",
"classnames": "^2.2.5",
"coriolis-data": "../coriolis-data",
"d3": "4.8.0",
"detect-browser": "^1.7.0",
"fbemitter": "^2.1.1",
"lodash": "^4.17.10",
"lz-string": "^1.4.4",
"pako": "^1.0.6",
"prop-types": "^15.5.8",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-ga": "^2.5.3",
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
"recharts": "^0.22.3",
"superagent": "^3.5.2"
}
}
{
"name": "coriolis_shipyard",
"version": "3.0.0",
"repository": {
"type": "git",
"url": "https://github.com/EDCD/coriolis"
},
"homepage": "https://coriolis.io",
"bugs": "https://github.com/EDCD/coriolis/issues",
"private": true,
"engine": "node >= 4.8.1",
"license": "MIT",
"scripts": {
"extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f",
"clean": "rimraf build",
"start": "node devServer.js",
"lint": "eslint --ext .js,.jsx src",
"test": "jest",
"prod-serve": "nginx -p $(pwd) -c nginx.conf",
"prod-stop": "kill -QUIT $(cat nginx.pid)",
"build": "npm run clean && cross-env NODE_ENV=production webpack -p --config webpack.config.prod.js",
"rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws",
"deploy": "npm run lint && npm test && npm run build && npm run rsync"
},
"jest": {
"transform": {
".*": "<rootDir>/node_modules/babel-jest"
},
"testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$",
"moduleFileExtensions": [
"js",
"json",
"jsx"
],
"automock": true,
"bail": false,
"unmockedModulePathPatterns": [
"<rootDir>/node_modules/lodash",
"<rootDir>/node_modules/react",
"<rootDir>/node_modules/react-dom",
"<rootDir>/node_modules/react-transition-group",
"<rootDir>/node_modules/react-testutils-additions",
"<rootDir>/node_modules/fbjs",
"<rootDir>/node_modules/fbemitter",
"<rootDir>/node_modules/classnames",
"<rootDir>/node_modules/d3",
"<rootDir>/node_modules/lz-string",
"<rootDir>/node_modules/jsen",
"coriolis-data",
"<rootDir>/src/app/shipyard",
"<rootDir>/src/app/i18n",
"<rootDir>/src/app/utils",
"<rootDir>/src/schemas",
"<rootDir>/__tests__"
]
},
"devDependencies": {
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/plugin-proposal-decorators": "^7.0.0",
"@babel/plugin-proposal-do-expressions": "^7.0.0",
"@babel/plugin-proposal-export-default-from": "^7.0.0",
"@babel/plugin-proposal-export-namespace-from": "^7.0.0",
"@babel/plugin-proposal-function-bind": "^7.0.0",
"@babel/plugin-proposal-function-sent": "^7.0.0",
"@babel/plugin-proposal-json-strings": "^7.0.0",
"@babel/plugin-proposal-logical-assignment-operators": "^7.0.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
"@babel/plugin-proposal-numeric-separator": "^7.0.0",
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
"@babel/plugin-proposal-pipeline-operator": "^7.0.0",
"@babel/plugin-proposal-throw-expressions": "^7.0.0",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-syntax-import-meta": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"appcache-webpack-plugin": "^1.4.0",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^10.0.1",
"babel-jest": "^23.6.0",
"babel-loader": "^8.0.0",
"copy-webpack-plugin": "^4.5.2",
"create-react-class": "^15.6.3",
"cross-env": "^5.2.0",
"css-loader": "^1.0.0",
"d3-selection": "^1.3.2",
"esdoc": "^1.1.0",
"esdoc-custom-theme": "^1.4.2",
"esdoc-ecmascript-proposal-plugin": "^1.0.0",
"esdoc-jsx-plugin": "^1.0.0",
"esdoc-publish-html-plugin": "^1.1.2",
"esdoc-react-plugin": "^1.0.1",
"esdoc-standard-plugin": "^1.0.0",
"eslint": "^5.6.0",
"eslint-plugin-react": "^7.11.1",
"expose-loader": "^0.7.5",
"express": "^4.16.3",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"file-loader": "^2.0.0",
"html-webpack-plugin": "^3.0.7",
"jest-cli": "^23.6.0",
"jsen": "^0.6.4",
"json-loader": "^0.5.4",
"less": "^3.8.1",
"less-loader": "^4.1.0",
"react-addons-perf": "^15.4.2",
"react-container-dimensions": "^1.4.1",
"react-testutils-additions": "^16.0.0",
"react-transition-group": "^2.5.0",
"rimraf": "^2.6.1",
"rollup": "^0.66.2",
"rollup-plugin-node-resolve": "^3.4.0",
"style-loader": "^0.23.0",
"uglify-js": "^3.4.9",
"url-loader": "^1.1.1",
"webpack": "^4.20.2",
"webpack-bugsnag-plugins": "^1.2.2",
"webpack-cli": "^3.1.1",
"webpack-dev-server": "^3.1.9",
"webpack-notifier": "^1.6.0",
"workbox-webpack-plugin": "^3.6.1"
},
"sideEffects": false,
"dependencies": {
"@babel/polyfill": "^7.0.0",
"browserify-zlib-next": "^1.0.1",
"classnames": "^2.2.6",
"coriolis-data": "../coriolis-data",
"d3": "^5.7.0",
"detect-browser": "^3.0.1",
"fbemitter": "^2.1.1",
"lodash": "^4.17.11",
"lz-string": "^1.4.4",
"pako": "^1.0.6",
"prop-types": "^15.6.2",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-extras": "^0.7.1",
"react-fuzzy": "^0.5.2",
"react-ga": "^2.5.3",
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
"recharts": "^1.2.0",
"register-service-worker": "^1.5.2",
"superagent": "^3.8.3"
}
}

View File

@@ -1,10 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import Router from './Router';
import { register } from 'register-service-worker';
import { EventEmitter } from 'fbemitter';
import { getLanguage } from './i18n/Language';
import Persist from './stores/Persist';
import Announcement from './components/Announcement';
import Header from './components/Header';
import Tooltip from './components/Tooltip';
import ModalExport from './components/ModalExport';
@@ -13,7 +15,6 @@ import ModalImport from './components/ModalImport';
import ModalPermalink from './components/ModalPermalink';
import * as CompanionApiUtils from './utils/CompanionApiUtils';
import * as JournalUtils from './utils/JournalUtils';
import AboutPage from './pages/AboutPage';
import NotFoundPage from './pages/NotFoundPage';
import OutfittingPage from './pages/OutfittingPage';
@@ -22,12 +23,12 @@ import ShipyardPage from './pages/ShipyardPage';
import ErrorDetails from './pages/ErrorDetails';
const zlib = require('pako');
const request = require('superagent');
/**
* Coriolis App
*/
export default class Coriolis extends React.Component {
static childContextTypes = {
closeMenu: PropTypes.func.isRequired,
hideModal: PropTypes.func.isRequired,
@@ -66,11 +67,12 @@ export default class Coriolis extends React.Component {
this.state = {
noTouch: !('ontouchstart' in window || navigator.msMaxTouchPoints || navigator.maxTouchPoints),
page: null,
announcements: [],
language: getLanguage(Persist.getLangCode()),
route: {},
sizeRatio: Persist.getSizeRatio()
};
this._getAnnouncements();
Router('', (r) => this._setPage(ShipyardPage, r));
Router('/import?', (r) => this._importBuild(r));
Router('/import/:data', (r) => this._importBuild(r));
@@ -103,12 +105,22 @@ export default class Coriolis extends React.Component {
}
r.params.ship = ship.id;
r.params.code = ship.toString();
this._setPage(OutfittingPage, r);
this._setPage(OutfittingPage, r)
} catch (err) {
this._onError('Failed to import ship', r.path, 0, 0, err);
}
}
async _getAnnouncements() {
try {
const announces = await request.get('https://orbis.zone/api/announcement')
.query({ showInCoriolis: true });
this.setState({ announcements: announces.body });
} catch (err) {
console.error(err)
}
}
/**
* Updates / Sets the page and route context
* @param {[type]} page The page to be shown
@@ -334,47 +346,37 @@ export default class Coriolis extends React.Component {
});
}
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
// Your service-worker.js *must* be located at the top-level directory relative to your site.
// It won't be able to control pages unless it's located at the same level or higher than them.
// *Don't* register service worker file in, e.g., a scripts/ sub-directory!
// See https://github.com/slightlyoff/ServiceWorker/issues/468
const self = this;
navigator.serviceWorker.register('/service-worker.js').then(function(reg) {
// updatefound is fired if service-worker.js changes.
reg.onupdatefound = function() {
// The updatefound event implies that reg.installing is set; see
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#service-worker-container-updatefound-event
var installingWorker = reg.installing;
installingWorker.onstatechange = function() {
switch (installingWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and the fresh content will
// have been added to the cache.
// It's the perfect time to display a "New content is available; please refresh."
// message in the page's interface.
console.log('New or updated content is available.');
self.setState({ appCacheUpdate: true }); // Browser downloaded a new app cache.
} else {
// At this point, everything has been precached.
// It's the perfect time to display a "Content is cached for offline use." message.
console.log('Content is now available offline!');
self.setState({ appCacheUpdate: true }); // Browser downloaded a new app cache.
}
break;
case 'redundant':
console.error('The installing service worker became redundant.');
break;
}
};
};
}).catch(function(e) {
console.error('Error during service worker registration:', e);
// Your service-worker.js *must* be located at the top-level directory relative to your site.
// It won't be able to control pages unless it's located at the same level or higher than them.
// *Don't* register service worker file in, e.g., a scripts/ sub-directory!
// See https://github.com/slightlyoff/ServiceWorker/issues/468
const self = this;
if (process.env.NODE_ENV === 'production') {
register('/service-worker.js', {
ready(registration) {
console.log('Service worker is active.');
},
registered(registration) {
console.log('Service worker has been registered.');
},
cached(registration) {
console.log('Content has been cached for offline use.');
},
updatefound(registration) {
console.log('New content is downloading.');
},
updated(registration) {
self.setState({ appCacheUpdate: true });
console.log('New content is available; please refresh.');
},
offline() {
console.log('No internet connection found. App is running in offline mode.');
},
error(error) {
console.error('Error during service worker registration:', error);
}
});
});
}
}
window.onerror = this._onError.bind(this);
window.addEventListener('resize', () => this.emitter.emit('windowResize'));
@@ -395,19 +397,23 @@ export default class Coriolis extends React.Component {
return <div style={{ minHeight: '100%' }} onClick={this._closeMenu}
className={this.state.noTouch ? 'no-touch' : null}>
<Header appCacheUpdate={this.state.appCacheUpdate} currentMenu={currentMenu}/>
<Header announcements={this.state.announcements} appCacheUpdate={this.state.appCacheUpdate}
currentMenu={currentMenu}/>
<div className="announcement-container">{this.state.announcements.map(a => <Announcement
text={a.message}/>)}</div>
{this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) :
<NotFoundPage/>}
{this.state.modal}
{this.state.tooltip}
<footer>
<div className="right cap">
<a href="https://github.com/EDCD/coriolis" target="_blank"
<a href="https://github.com/EDCD/coriolis" target="_blank" rel="noopener noreferrer"
title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
<br/>
<a
href={'https://github.com/EDCD/coriolis/compare/edcd:develop@{' + window.CORIOLIS_DATE + '}...edcd:develop'}
target="_blank" title={'Coriolis Commits since' + window.CORIOLIS_DATE}>Commits since last release
target="_blank" rel="noopener noreferrer" title={'Coriolis Commits since' + window.CORIOLIS_DATE}>Commits
since last release
({window.CORIOLIS_DATE})</a>
</div>
</footer>

View File

@@ -1,5 +1,6 @@
import Persist from './stores/Persist';
import ReactGA from 'react-ga';
ReactGA.initialize('UA-55840909-18');
let standalone = undefined;

View File

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

View File

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

View File

@@ -5,6 +5,7 @@ import TranslatedComponent from './TranslatedComponent';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames';
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import FuzzySearch from 'react-fuzzy';
const PRESS_THRESHOLD = 500; // mouse/touch down threshold
@@ -39,7 +40,7 @@ const GRPCAT = {
'mc': 'projectiles',
'axmc': 'experimental',
'fc': 'projectiles',
'rfl': 'experimental',
'rfl': 'experimental',
'pa': 'projectiles',
'rg': 'projectiles',
'mr': 'ordnance',
@@ -72,7 +73,12 @@ const GRPCAT = {
'gfsb': 'guardian',
'gmrp': 'guardian',
'gsc': 'guardian',
'ghrp': 'guardian'
'ghrp': 'guardian',
// Mining
'scl': 'mining',
'pwa': 'mining',
'sdm': 'mining'
};
// Order here is the order in which items will be shown in the modules menu
const CATEGORIES = {
@@ -89,7 +95,7 @@ const CATEGORIES = {
'structural reinforcement': ['hr', 'mrp'],
'dc': ['dc'],
// Hardpoints
'lasers': ['pl', 'ul', 'bl', 'ml'],
'lasers': ['pl', 'ul', 'bl'],
'projectiles': ['mc', 'c', 'fc', 'pa', 'rg'],
'ordnance': ['mr', 'tp', 'nl'],
// Utilities
@@ -98,17 +104,18 @@ const CATEGORIES = {
'defence': ['ch', 'po', 'ec'],
'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners
// Experimental
'experimental': ['axmc', 'axmr', 'rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr', ],
'experimental': ['axmc', 'axmr', 'rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr',],
// Guardian
'guardian': ['gpp', 'gpd', 'gpc', 'ggc', 'gsrp', 'gfsb', 'ghrp', 'gmrp', 'gsc']
'guardian': ['gpp', 'gpd', 'gpc', 'ggc', 'gsrp', 'gfsb', 'ghrp', 'gmrp', 'gsc'],
'mining': ['ml', 'scl', 'pwa', 'sdm', 'abl'],
};
/**
* Available modules menu
*/
export default class AvailableModulesMenu extends TranslatedComponent {
static propTypes = {
modules: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
onSelect: PropTypes.func.isRequired,
@@ -134,6 +141,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
constructor(props, context) {
super(props);
this._hideDiff = this._hideDiff.bind(this);
this._showSearch = this._showSearch.bind(this);
this.state = this._initState(props, context);
this.slotItems = [];// Array to hold <li> refs.
}
@@ -159,8 +167,8 @@ export default class AvailableModulesMenu extends TranslatedComponent {
this._hideDiff(event);
onSelect(m);
}
);
);
let fuzzy = [];
if (modules instanceof Array) {
list = buildGroup(modules[0].grp, modules);
} else {
@@ -168,9 +176,11 @@ export default class AvailableModulesMenu extends TranslatedComponent {
// At present time slots with grouped options (Hardpoints and Internal) can be empty
if (m) {
let emptyId = 'empty';
if(this.firstSlotId == null) this.firstSlotId = emptyId;
if (this.firstSlotId == null) this.firstSlotId = emptyId;
let keyDown = this._keyDown.bind(this, onSelect);
list.push(<div className='empty-c upp' key={emptyId} data-id={emptyId} onClick={onSelect.bind(null, null)} onKeyDown={keyDown} tabIndex="0" ref={slotItem => this.slotItems[emptyId] = slotItem} >{translate('empty')}</div>);
list.push(<div className='empty-c upp' key={emptyId} data-id={emptyId} onClick={onSelect.bind(null, null)}
onKeyDown={keyDown} tabIndex="0"
ref={slotItem => this.slotItems[emptyId] = slotItem}>{translate('empty')}</div>);
}
// Need to regroup the modules by our own categorisation
@@ -198,7 +208,8 @@ export default class AvailableModulesMenu extends TranslatedComponent {
if (categories.length === 1) {
// Show category header instead of group header
if (m && grp == m.grp) {
list.push(<div ref={(elem) => this.groupElem = elem} key={category} className={'select-category upp'}>{translate(category)}</div>);
list.push(<div ref={(elem) => this.groupElem = elem} key={category}
className={'select-category upp'}>{translate(category)}</div>);
} else {
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
}
@@ -209,19 +220,32 @@ export default class AvailableModulesMenu extends TranslatedComponent {
categoryHeader = true;
}
if (m && grp == m.grp) {
list.push(<div ref={(elem) => this.groupElem = elem} key={grp} className={'select-group cap'}>{translate(grp)}</div>);
list.push(<div ref={(elem) => this.groupElem = elem} key={grp}
className={'select-group cap'}>{translate(grp)}</div>);
} else {
list.push(<div key={grp} className={'select-group cap'}>{translate(grp)}</div>);
}
}
list.push(buildGroup(grp, modules[grp]));
for (const i of modules[grp]) {
let mount = '';
if (i.mount === 'F') {
mount = 'Fixed';
} else if (i.mount === 'G') {
mount = 'Gimballed';
} else if (i.mount === 'T') {
mount = 'Turreted';
}
const fuzz = { grp, m: i, name: `${i.class}${i.rating}${mount ? ' ' + mount : ''} ${translate(grp)}` };
fuzzy.push(fuzz);
}
}
}
}
}
}
let trackingFocus = false;
return { list, currentGroup, trackingFocus };
return { list, currentGroup, fuzzy, trackingFocus };
}
/**
@@ -243,9 +267,11 @@ export default class AvailableModulesMenu extends TranslatedComponent {
const sortedModules = modules.sort(this._moduleOrder);
// Calculate the number of items per class. Used so we don't have long lists with only a few items in each row
const tmp = sortedModules.map((v, i) => v['class']).reduce((count, cls) => { count[cls] = ++count[cls] || 1; return count; }, {});
const tmp = sortedModules.map((v, i) => v['class']).reduce((count, cls) => {
count[cls] = ++count[cls] || 1;
return count;
}, {});
const itemsPerClass = Math.max.apply(null, Object.keys(tmp).map(key => tmp[key]));
let itemsOnThisRow = 0;
@@ -298,22 +324,29 @@ export default class AvailableModulesMenu extends TranslatedComponent {
};
}
switch(m.mount) {
case 'F': mount = <MountFixed className={'lg'} />; break;
case 'G': mount = <MountGimballed className={'lg'}/>; break;
case 'T': mount = <MountTurret className={'lg'}/>; break;
switch (m.mount) {
case 'F':
mount = <MountFixed className={'lg'}/>;
break;
case 'G':
mount = <MountGimballed className={'lg'}/>;
break;
case 'T':
mount = <MountTurret className={'lg'}/>;
break;
}
if (m.name && m.name === prevName) {
// elems.push(<br key={'b' + m.grp + i} />);
itemsOnThisRow = 0;
}
if (itemsOnThisRow == 6 || i > 0 && sortedModules.length > 3 && itemsPerClass > 2 && m.class != prevClass && (m.rating != prevRating || m.mount)) {
elems.push(<br key={'b' + m.grp + i} />);
elems.push(<br key={'b' + m.grp + i}/>);
itemsOnThisRow = 0;
}
let tbIdx = (classes.indexOf('disabled') < 0) ? 0 : undefined;
elems.push(
<li key={m.id} data-id={m.id} className={classes} {...eventHandlers} tabIndex={tbIdx} ref={slotItem => this.slotItems[m.id] = slotItem}>
<li key={m.id} data-id={m.id} className={classes} {...eventHandlers} tabIndex={tbIdx}
ref={slotItem => this.slotItems[m.id] = slotItem}>
{mount}
{(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')}
</li>
@@ -341,6 +374,40 @@ export default class AvailableModulesMenu extends TranslatedComponent {
}
}
/**
* Generate tooltip content for the difference between the
* mounted module and the hovered modules
*/
_showSearch() {
if (this.props.modules instanceof Array) {
return;
}
return (
<FuzzySearch
list={this.state.fuzzy}
keys={['grp', 'name']}
tokenize={true}
className={'input'}
width={'100%'}
style={{ padding: 0 }}
onSelect={e => this.props.onSelect.bind(null, e.m)()}
resultsTemplate={(props, state, styles, clickHandler) => {
return state.results.map((val, i) => {
return (
<div
key={i}
className={'lc'}
onClick={() => clickHandler(i)}
>
{val.name}
</div>
);
});
}}
/>
);
}
/**
* Mouse over diff handler
* @param {Function} showDiff diff tooltip callback
@@ -406,7 +473,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
* @param {Function} select Select module callback
* @param {SytheticEvent} event Event
*/
_keyUp(select,event) {
_keyUp(select, event) {
// nothing here yet
}
@@ -476,12 +543,13 @@ export default class AvailableModulesMenu extends TranslatedComponent {
this.slotItems[this.firstSlotId].focus();
}
}
/**
* Handle focus if the component updates
*
*/
componentWillUnmount() {
if(this.props.slotDiv) {
if (this.props.slotDiv) {
this.props.slotDiv.focus();
}
}
@@ -502,14 +570,14 @@ export default class AvailableModulesMenu extends TranslatedComponent {
render() {
return (
<div ref={node => this.node = node}
className={cn('select', this.props.className)}
onScroll={this._hideDiff}
onClick={(e) => e.stopPropagation() }
onContextMenu={stopCtxPropagation}
className={cn('select', this.props.className)}
onScroll={this._hideDiff}
onClick={(e) => e.stopPropagation()}
onContextMenu={stopCtxPropagation}
>
{this._showSearch()}
{this.state.list}
</div>
);
}
}

View File

@@ -38,7 +38,6 @@ function insertLinebreaks(d) {
* Bar Chart
*/
export default class BarChart extends TranslatedComponent {
static defaultProps = {
colors: ['#7b6888', '#6b486b', '#3182bd', '#a05d56', '#d0743c'],
labels: null,

View File

@@ -1,13 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import { nameComparator } from '../utils/SlotFunctions';
import { Pip } from './SvgIcons';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
/**
* Boost displays a boost button that toggles bosot

View File

@@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import Slider from '../components/Slider';
/**

View File

@@ -10,7 +10,6 @@ import { outfitURL } from '../utils/UrlGenerators';
* Comparison Table
*/
export default class ComparisonTable extends TranslatedComponent {
static propTypes = {
facets: PropTypes.array.isRequired,
builds: PropTypes.array.isRequired,

View File

@@ -13,7 +13,6 @@ import { ShoppingIcon } from '../components/SvgIcons';
* Cost Section
*/
export default class CostSection extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
code: PropTypes.string.isRequired,
@@ -361,11 +360,11 @@ export default class CostSection extends TranslatedComponent {
for (let i = 0, l = retrofitCosts.length; i < l; i++) {
let item = retrofitCosts[i];
rows.push(<tr key={i} className={cn('highlight', { disabled: !item.retroItem.incCost })} onClick={this._toggleRetrofitCost.bind(this, item)}>
<td className='ptr' style={{ width: '1em' }}>{item.sellClassRating}</td>
<td className='le ptr shorten cap'>{translate(item.sellName)}</td>
<td className='ptr' style={{ width: '1em' }}>{item.buyClassRating}</td>
<td className='le ptr shorten cap'>{translate(item.buyName)}</td>
<td colSpan='2' className={cn('ri ptr', item.retroItem.incCost ? item.netCost > 0 ? 'warning' : 'secondary-disabled' : 'disabled')}>{int(item.netCost)}{units.CR}</td>
<td className='ptr' style={{ width: '1em' }}>{item.sellClassRating}</td>
<td className='le ptr shorten cap'>{translate(item.sellName)}</td>
<td className='ptr' style={{ width: '1em' }}>{item.buyClassRating}</td>
<td className='le ptr shorten cap'>{translate(item.buyName)}</td>
<td colSpan='2' className={cn('ri ptr', item.retroItem.incCost ? item.netCost > 0 ? 'warning' : 'secondary-disabled' : 'disabled')}>{int(item.netCost)}{units.CR}</td>
</tr>);
}
} else {

View File

@@ -52,12 +52,12 @@ export default class Defence extends TranslatedComponent {
* @return {React.Component} contents
*/
render() {
const { ship, sys, opponentWep } = this.props;
const { opponent, sys, opponentWep } = this.props;
const { language, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { shield, armour, shielddamage, armourdamage } = this.state;
const pd = ship.standard[4].m;
const pd = opponent.standard[4].m;
const shieldSourcesData = [];
const effectiveShieldData = [];
@@ -226,26 +226,26 @@ export default class Defence extends TranslatedComponent {
return (
<span id='defence'>
{shield.total ? <span>
<div className='group quarter'>
<h2>{translate('shield metrics')}</h2>
<br/>
<h2 onMouseOver={termtip.bind(null, <div>{shieldSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')}<br/>{formats.int(shield.total)}{units.MJ}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_SHIELDS')}<br/>{shielddamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(shield.total, shielddamage.totalsdps, shielddamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}</h2>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECOVER'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECOVER_SHIELDS')}<br/>{shield.recover === Math.Inf ? translate('never') : formats.time(shield.recover)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECHARGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')}<br/>{shield.recharge === Math.Inf ? translate('never') : formats.time(shield.recharge)}</h2>
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield sources')}</h2>
<PieChart data={shieldSourcesData} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_DAMAGE_TAKEN'))} onMouseOut={tooltip.bind(null, null)}>{translate('damage taken')}(%)</h2>
<VerticalBarChart data={shieldDamageTakenData} yMax={140} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_SHIELD'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective shield')}(MJ)</h2>
<VerticalBarChart data={effectiveShieldData} yMax={maxEffectiveShield}/>
</div>
<div className='group quarter'>
<h2>{translate('shield metrics')}</h2>
<br/>
<h2 onMouseOver={termtip.bind(null, <div>{shieldSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')}<br/>{formats.int(shield.total)}{units.MJ}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_SHIELDS')}<br/>{shielddamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(shield.total, shielddamage.totalsdps, shielddamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}</h2>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECOVER'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECOVER_SHIELDS')}<br/>{shield.recover === Math.Inf ? translate('never') : formats.time(shield.recover)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECHARGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')}<br/>{shield.recharge === Math.Inf ? translate('never') : formats.time(shield.recharge)}</h2>
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield sources')}</h2>
<PieChart data={shieldSourcesData} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_DAMAGE_TAKEN'))} onMouseOut={tooltip.bind(null, null)}>{translate('damage taken')}(%)</h2>
<VerticalBarChart data={shieldDamageTakenData} yMax={140} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_SHIELD'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective shield')}(MJ)</h2>
<VerticalBarChart data={effectiveShieldData} yMax={maxEffectiveShield}/>
</div>
</span> : null }
<div className='group quarter'>

View File

@@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import Slider from '../components/Slider';
/**

View File

@@ -1,12 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import { nameComparator } from '../utils/SlotFunctions';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
import * as Calc from '../shipyard/Calculations';
/**

View File

@@ -1,12 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import { nameComparator } from '../utils/SlotFunctions';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
import * as Calc from '../shipyard/Calculations';
/**
@@ -69,7 +64,7 @@ export default class FSDProfile extends TranslatedComponent {
const { formats, translate, units } = language;
const { ship, cargo, fuel } = this.props;
// Calculate bounds for our line chart - use thruster info for X
const thrusters = ship.standard[1].m;
const fsd = ship.standard[2].m;

View File

@@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import Slider from '../components/Slider';
/**

View File

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

View File

@@ -1,7 +1,6 @@
import React from 'react';
import SlotSection from './SlotSection';
import HardpointSlot from './HardpointSlot';
import cn from 'classnames';
import { MountFixed, MountGimballed, MountTurret } from '../components/SvgIcons';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
@@ -154,6 +153,15 @@ export default class HardpointSlotSection extends SlotSection {
<ul>
<li className='lc' tabIndex='0' onClick={_fill.bind(this, 'nl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['nl-F'] = smRef}>{translate('nl')}</li>
</ul>
<div className='select-group cap'>{translate('ggc')}</div>
<ul>
<li className='lc' tabIndex='0' onClick={_fill.bind(this, 'ggc', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ggc-F'] = smRef}>{translate('ggc')}</li>
</ul>
<div className='select-group cap'>{translate('rfl')}</div>
<ul>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'rfl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['rfl-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'rfl', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['rfl-T'] = smRef}><MountTurret className='lg'/></li>
</ul>
</div>;
}

View File

@@ -16,6 +16,7 @@ import ModalExport from './ModalExport';
import ModalHelp from './ModalHelp';
import ModalImport from './ModalImport';
import Slider from './Slider';
import Announcement from './Announcement';
import { outfitURL } from '../utils/UrlGenerators';
const SIZE_MIN = 0.65;
@@ -76,8 +77,11 @@ export default class Header extends TranslatedComponent {
this._openShips = this._openMenu.bind(this, 's');
this._openBuilds = this._openMenu.bind(this, 'b');
this._openComp = this._openMenu.bind(this, 'comp');
this._openAnnounce = this._openMenu.bind(this, 'announce');
this._getAnnouncementsMenu = this._getAnnouncementsMenu.bind(this);
this._openSettings = this._openMenu.bind(this, 'settings');
this._showHelp = this._showHelp.bind(this);
this.update = this.update.bind(this);
this.languageOptions = [];
this.insuranceOptions = [];
this.state = {
@@ -345,7 +349,7 @@ export default class Header extends TranslatedComponent {
_getShipsMenu() {
let shipList = [];
for (let s in Ships) {
for (let s of this.shipOrder) {
shipList.push(<ActiveLink key={s} href={outfitURL(s)} className='block'>{Ships[s].properties.name}</ActiveLink>);
}
@@ -411,6 +415,29 @@ export default class Header extends TranslatedComponent {
);
}
/**
* Generate the announcement menu
* @return {React.Component} Menu
*/
_getAnnouncementsMenu() {
let announcements;
let translate = this.context.language.translate;
if (this.props.announcements) {
announcements = [];
for (let announce of this.props.announcements) {
announcements.push(<Announcement text={announce.message} />);
announcements.push(<hr/>);
}
}
return (
<div className='menu-list' onClick={ (e) => e.stopPropagation() } style={{ whiteSpace: 'nowrap' }}>
{announcements}
<hr />
</div>
);
}
/**
* Generate the settings menu
* @return {React.Component} Menu
@@ -534,6 +561,15 @@ export default class Header extends TranslatedComponent {
}
}
async update() {
const reg = await navigator.serviceWorker.getRegistration();
if (!reg || !reg.waiting) {
return window.location.reload();
}
reg.waiting.postMessage('skipWaiting');
window.location.reload();
}
/**
* Render the header
* @return {React.Component} Header
@@ -544,7 +580,7 @@ export default class Header extends TranslatedComponent {
let hasBuilds = Persist.hasBuilds();
return (
<header>
{this.props.appCacheUpdate && <div id="app-update" onClick={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>}
{this.props.appCacheUpdate && <div id="app-update" onClick={this.update}>{translate('PHRASE_UPDATE_RDY')}</div>}
{this.props.appCacheUpdate ? <a className={'view-changes'} href={'https://github.com/EDCD/coriolis/compare/edcd:develop@{' + window.CORIOLIS_DATE + '}...edcd:develop'} target="_blank">
{'View Release Changes'}
</a> : null}
@@ -571,6 +607,13 @@ export default class Header extends TranslatedComponent {
{openedMenu == 'comp' ? this._getComparisonsMenu() : null}
</div>
<div className='l menu'>
<div className={cn('menu-header', { selected: openedMenu == 'announce', disabled: this.props.announcements.length === 0})} onClick={this.props.announcements.length !== 0 && this._openAnnounce}>
<span className='menu-item-label'>{translate('announcements')}</span>
</div>
{openedMenu == 'announce' ? this._getAnnouncementsMenu() : null}
</div>
{window.location.origin.search('.edcd.io') >= 0 ?
<div className='l menu'>
<a href="https://youtu.be/4SvnLcefhtI" target="_blank">

View File

@@ -1,5 +1,4 @@
import React from 'react';
import cn from 'classnames';
import SlotSection from './SlotSection';
import InternalSlot from './InternalSlot';
import * as ModuleUtils from '../shipyard/ModuleUtils';

View File

@@ -1,12 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import { nameComparator } from '../utils/SlotFunctions';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
import * as Calc from '../shipyard/Calculations';
/**

View File

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

View File

@@ -23,8 +23,10 @@ export default class ModalOrbis extends TranslatedComponent {
this.state = {
orbisCreds: Persist.getOrbisCreds(),
orbisUrl: '...',
ship: this.props.ship,
authenticatedStatus: 'Checking...'
};
this.orbisCategory = this.orbisCategory.bind(this);
}
/**
@@ -88,6 +90,17 @@ export default class ModalOrbis extends TranslatedComponent {
});
}
/**
* Handler for changing category
* @param {SyntheticEvent} e React Event
*/
orbisCategory(e) {
let ship = this.state.ship;
let cat = e.target.value;
ship.category = cat;
this.setState({ship});
}
/**
* Render the modal
* @return {React.Component} Modal Content
@@ -106,6 +119,17 @@ export default class ModalOrbis extends TranslatedComponent {
<br/><br/>
<a className='button' href="https://orbis.zone/api/auth">Log in / signup to Orbis</a>
<br/><br/>
<h3>Category</h3>
<select onChange={this.orbisCategory}>
<option value="">No Category</option>
<option>Combat</option>
<option>Mining</option>
<option>Trading</option>
<option>Exploration</option>
<option>Passenger Liner</option>
<option>PvP</option>
</select>
<br/><br/>
<h3 >{translate('Orbis link')}</h3>
<input value={this.state.orbisUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/>
<br/><br/>

View File

@@ -109,17 +109,18 @@ export default class ModalShoppingList extends TranslatedComponent {
*/
sendToEDEng(event) {
event.preventDefault();
let translate = this.context.language.translate;
const target = event.target;
target.disabled = this.state.blueprints.length > 0;
if (this.state.blueprints.length === 0) {
target.innerText = 'No modded components.';
target.innerText = translate('No modded components.');
target.disabled = true;
setTimeout(() => {
target.innerText = 'Send to EDEngineer';
target.innerText = translate('Send to EDEngineer');
target.disabled = false;
}, 3000);
} else {
target.innerText = 'Sending...';
target.innerText = translate('Sending...');
}
let countSent = 0;
let countTotal = this.state.blueprints.length;
@@ -139,7 +140,7 @@ export default class ModalShoppingList extends TranslatedComponent {
countSent++;
if (countSent === countTotal) {
target.disabled = false;
target.innerText = 'Send to EDEngineer';
target.innerText = translate('Send to EDEngineer');
}
});
}
@@ -230,32 +231,32 @@ export default class ModalShoppingList extends TranslatedComponent {
this.sendToEDEng = this.sendToEDEng.bind(this);
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2>{translate('PHRASE_SHOPPING_MATS')}</h2>
<label>Grade 1 rolls </label>
<label>{translate('Grade 1 rolls ')}</label>
<input id={1} type={'number'} min={0} defaultValue={this.state.matsPerGrade[1]} onChange={this.changeHandler} />
<br/>
<label>Grade 2 rolls </label>
<label>{translate('Grade 2 rolls ')}</label>
<input id={2} type={'number'} min={0} defaultValue={this.state.matsPerGrade[2]} onChange={this.changeHandler} />
<br/>
<label>Grade 3 rolls </label>
<label>{translate('Grade 3 rolls ')}</label>
<input id={3} type={'number'} min={0} value={this.state.matsPerGrade[3]} onChange={this.changeHandler} />
<br/>
<label>Grade 4 rolls </label>
<label>{translate('Grade 4 rolls ')}</label>
<input id={4} type={'number'} min={0} value={this.state.matsPerGrade[4]} onChange={this.changeHandler} />
<br/>
<label>Grade 5 rolls </label>
<label>{translate('Grade 5 rolls ')}</label>
<input id={5} type={'number'} min={0} value={this.state.matsPerGrade[5]} onChange={this.changeHandler} />
<div>
<textarea className='cb json' readOnly value={this.state.matsList} />
</div>
<label hidden={!compatible} className={'l cap'}>CMDR Name </label>
<label hidden={!compatible} className={'l cap'}>{translate('CMDR Name')}</label>
<br/>
<select hidden={!compatible} className={'cmdr-select l cap'} onChange={this.cmdrChangeHandler} defaultValue={this.state.cmdrName}>
{this.state.cmdrs.map(e => <option key={e}>{e}</option>)}
</select>
<br/>
<p hidden={!this.state.failed} id={'failed'} className={'l'}>Failed to send to EDEngineer (Launch EDEngineer and make sure the API is started then refresh the page.)</p>
<p hidden={compatible} id={'browserbad'} className={'l'}>Sending to EDEngineer is not compatible with Firefox's security settings. Please try again with Chrome.</p>
<button className={'l cb dismiss cap'} disabled={!!this.state.failed || !compatible} onClick={this.sendToEDEng}>{translate('Send To EDEngineer')}</button>
<p hidden={!this.state.failed} id={'failed'} className={'l'}>{translate('PHRASE_FAIL_EDENGINEER')}</p>
<p hidden={compatible} id={'browserbad'} className={'l'}>{translate('PHRASE_FIREFOX_EDENGINEER')}</p>
<button className={'l cb dismiss cap'} disabled={!!this.state.failed || !compatible} onClick={this.sendToEDEng}>{translate('Send to EDEngineer')}</button>
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
</div>;
}

View File

@@ -9,7 +9,6 @@ import { isValueBeneficial } from '../utils/BlueprintFunctions';
* Modification
*/
export default class Modification extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
m: PropTypes.object.isRequired,
@@ -39,10 +38,24 @@ export default class Modification extends TranslatedComponent {
* in a value by hand
*/
_updateValue(value) {
let { m, name, ship } = this.props;
value = Math.max(Math.min(value, 50000), -50000);
ship.setModification(m, name, value, true, true);
this.setState({ value });
let reCast = String(Number(value));
if (reCast.endsWith(value) || reCast.startsWith(value)) {
let { m, name, ship } = this.props;
value = Math.max(Math.min(value, 50000), -50000);
ship.setModification(m, name, value, true, true);
}
}
/**
* Triggered when a key is pressed down with focus on the number editor.
* @param {SyntheticEvent} event Key down event
*/
_keyDown(event) {
if (event.key == 'Enter') {
this._updateFinished();
}
this.props.onKeyDown(event);
}
/**
@@ -72,6 +85,11 @@ export default class Modification extends TranslatedComponent {
return null;
}
let inputClassNames = {
'cb': true,
'greyed-out': !this.props.highlight
};
return (
<div onBlur={this._updateFinished.bind(this)} key={name}
className={cn('cb', 'modification-container')}
@@ -84,24 +102,24 @@ export default class Modification extends TranslatedComponent {
<td className={'input-container'}>
<span>
{this.props.editable ?
<NumberEditor className={'cb'} value={this.state.value}
<NumberEditor className={cn(inputClassNames)} value={this.state.value}
decimals={2} style={{ textAlign: 'right' }} step={0.01}
stepModifier={1} onKeyDown={ this.props.onKeyDown }
stepModifier={1} onKeyDown={this._keyDown.bind(this)}
onValueChange={this._updateValue.bind(this)} /> :
<input type="text" value={formats.f2(this.state.value)}
disabled className={'number-editor'}
disabled className={cn('number-editor', 'greyed-out')}
style={{ textAlign: 'right', cursor: 'inherit' }}/>
}
<span className={'unit-container'}>
{units[m.getStoredUnitFor(name)]}
{units[m.getStoredUnitFor(name)]}
</span>
</span>
</td>
<td style={{ textAlign: 'center' }} className={
modValue ?
isValueBeneficial(name, modValue) ? 'secondary': 'warning':
''
}>
modValue ?
isValueBeneficial(name, modValue) ? 'secondary' : 'warning' :
''
}>
{formats.f2(modValue / 100) || 0}%
</td>
</tr>

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import * as _ from 'lodash';
import TranslatedComponent from './TranslatedComponent';
import { isEmpty, stopCtxPropagation } from '../utils/UtilityFunctions';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames';
import { Modifications } from 'coriolis-data/dist';
import Modification from './Modification';
@@ -23,7 +23,6 @@ const MODIFICATIONS_COMPARATOR = (mod1, mod2) => {
* Modifications menu
*/
export default class ModificationsMenu extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
m: PropTypes.object.isRequired,
@@ -214,11 +213,11 @@ export default class ModificationsMenu extends TranslatedComponent {
for (const modName of Modifications.modules[m.grp].modifications) {
if (!Modifications.modifications[modName].hidden) {
const key = modName + (m.getModValue(modName) / 100 || 0);
const editable = modName !== 'fallofffromrange' &&
m.blueprint.grades[m.blueprint.grade].features[modName];
const editable = modName !== 'fallofffromrange';
const highlight = m.blueprint.grades[m.blueprint.grade].features[modName];
this.lastNeId = modName;
(editable ? modifiableModifications : modifications).push(
<Modification key={ key } ship={ ship } m={ m }
(editable && highlight ? modifiableModifications : modifications).push(
<Modification key={ key } ship={ ship } m={ m } highlight={highlight}
value={m.getPretty(modName) || 0} modItems={this.modItems}
onChange={onChange} onKeyDown={this._keyDown} name={modName}
editable={editable} handleModChange = {this._handleModChange} />
@@ -461,10 +460,10 @@ export default class ModificationsMenu extends TranslatedComponent {
}
return (
<div
className={cn('select', this.props.className)}
onClick={(e) => e.stopPropagation() }
onContextMenu={stopCtxPropagation}
ref={modItem => this.modItems['modMainDiv'] = modItem}
className={cn('select', this.props.className)}
onClick={(e) => e.stopPropagation() }
onContextMenu={stopCtxPropagation}
ref={modItem => this.modItems['modMainDiv'] = modItem}
>
{ showBlueprintsMenu | showSpecialsMenu ? '' : haveBlueprint ?
<div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{blueprintLabel}</div> :
@@ -473,19 +472,19 @@ export default class ModificationsMenu extends TranslatedComponent {
{ showSpecial & !showSpecialsMenu ? <div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={specialTt ? termtip.bind(null, specialTt) : null} onMouseOut={specialTt ? tooltip.bind(null, null) : null} onClick={_toggleSpecialsMenu} onKeyDown={ this._keyDown }>{specialLabel}</div> : null }
{ showSpecialsMenu ? specials : null }
{ showReset ? <div tabIndex="0" className={'section-menu button-inline-menu warning'} style={{ cursor: 'pointer' }} onClick={_reset} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </div> : null }
{ showRolls ?
{ showRolls ?
<table style={{ width: '100%', backgroundColor: 'transparent' }}>
<tbody>
{ showRolls ?
<table style={{ width: '100%', backgroundColor: 'transparent' }}>
<tbody>
{ showRolls ?
<tr>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: false }) }> { translate('roll') }: </td>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: false }) }> { translate('mroll') }: </td>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 0 }) } style={{ cursor: 'pointer' }} onClick={_rollWorst} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('0%') } </td>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 50 })} style={{ cursor: 'pointer' }} onClick={_rollFifty} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_FIFTY')} onMouseOut={tooltip.bind(null, null)}> { translate('50%') } </td>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 100 })} style={{ cursor: 'pointer' }} onClick={_rollFull} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('100%') } </td>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === null || blueprintCv % 50 != 0 })} style={{ cursor: 'pointer' }} onClick={_rollRandom} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td>
</tr> : null }
</tbody>
</tbody>
</table> : null }
{ showMods ? <hr /> : null }
{ showMods ?

View File

@@ -35,13 +35,13 @@ export default class Movement extends TranslatedComponent {
return (
<span id='movement'>
<svg viewBox='0 0 600 600' fillRule="evenodd" clipRule="evenodd">
// Axes
{/* Axes */}
<path d="M150 250v300" strokeWidth='1'/>
<path d="M150 250l236 236" strokeWidth='1'/>
<path d="M150 250l350 -200" strokeWidth='1'/>
// End Arrow
{/* End Arrow */}
<path d="M508 43.3L487 67l-10-17.3 31-6.4z"/>
// Axes arcs and arrows
{/* Axes arcs and arrows */}
<path d="M71.7 251.7C64.2 259.2 60 269.4 60 280c0 22 18 40 40 40s40-18 40-40c0-10.6-4.2-20.8-11.7-28.3 7.5 7.5 11.7 17.7 11.7 28.3 0 22-18 40-40 40s-40-18-40-40c0-10.6 4.2-20.8 11.7-28.3z" strokeWidth='4' transform="matrix(.6 0 0 .3 87.5 376.3)"/>
<path d="M142.8 453l-13.2 8.7-2.6-9.7 15.8 1z"/>
<path d="M144.7 451.6l.5 1.6-16.2 10.6h-.4l-3.5-13 .7-.4 19.3 1.2zm-14.2 7.7l7.7-5-9.2-.7 1.5 5.7zm25.7-6.3l15.8-1-2.6 9.7-13.2-8.8z"/>
@@ -57,13 +57,13 @@ export default class Movement extends TranslatedComponent {
<path d="M359.5 422.4l-1.2 19.3-1.6.4-10.7-16 .2-.2 13-3.4.3.4zm-9 5l5.2 7.8.6-9.3-5.7 1.2zm-10.5 24l-13.2 8.6-2.6-9.7 15.8 1z"/>
<path d="M342 450l.4 1.5-16.2 10.7-.4-.2-3.5-13 .3-.3L342 450zm-14.3 7.6l7.7-5-9.2-.6 1.5 5.6z"/>
// Speed
{/* Speed */}
<text x="470" y="30" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcSpeed(eng, fuel, cargo, boost)) + 'm/s' : '-'}</text>
// Pitch
{/* Pitch */}
<text x="355" y="410" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcPitch(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
// Roll
{/* Roll */}
<text x="450" y="110" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcRoll(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
// Yaw
{/* Yaw */}
<text x="160" y="430" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcYaw(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
</svg>
</span>);

View File

@@ -5,7 +5,6 @@ import * as Calc from '../shipyard/Calculations';
import PieChart from './PieChart';
import { nameComparator } from '../utils/SlotFunctions';
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import VerticalBarChart from './VerticalBarChart';
/**
* Generates an internationalization friendly weapon comparator that will
@@ -203,9 +202,9 @@ export default class Offence extends TranslatedComponent {
let totalSEps = 0;
let totalSDpsObject = {'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0};
let shieldsSDpsObject = {'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0};
let armourSDpsObject = {'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0};
let totalSDpsObject = { 'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0 };
let shieldsSDpsObject = { 'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0 };
let armourSDpsObject = { 'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0 };
const rows = [];
for (let i = 0; i < damage.length; i++) {
@@ -267,22 +266,22 @@ export default class Offence extends TranslatedComponent {
return (
<span id='offence'>
<div className='group full'>
<table>
<thead>
<tr className='main'>
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</th>
<th colSpan='1'>{translate('overall')}</th>
<th colSpan='2'>{translate('opponent\'s shields')}</th>
<th colSpan='2'>{translate('opponent\'s armour')}</th>
</tr>
<tr>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_SHIELDS')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'es')}>{'eft'}</th>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'esdpsh')}>{'sdps'}</th>
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'eh')}>{'eft'}</th>
</tr>
</thead>
<table>
<thead>
<tr className='main'>
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</th>
<th colSpan='1'>{translate('overall')}</th>
<th colSpan='2'>{translate('opponent\'s shields')}</th>
<th colSpan='2'>{translate('opponent\'s armour')}</th>
</tr>
<tr>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_SHIELDS')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'es')}>{'eft'}</th>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'esdpsh')}>{'sdps'}</th>
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'eh')}>{'eft'}</th>
</tr>
</thead>
<tbody>
{rows}
{rows.length > 0 &&
@@ -296,7 +295,7 @@ export default class Offence extends TranslatedComponent {
</tr>
}
</tbody>
</table>
</table>
</div>
<div className='group quarter'>
<h2>{translate('offence metrics')}</h2>

View File

@@ -1,8 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { Ships } from 'coriolis-data/dist';
import Ship from '../shipyard/Ship';
import Persist from '../stores/Persist';
import TranslatedComponent from './TranslatedComponent';
import PowerManagement from './PowerManagement';
@@ -176,7 +174,7 @@ export default class OutfittingSubpages extends TranslatedComponent {
<th style={{ width:'25%' }} className={cn({ active: tab == 'power' })} onClick={this._showTab.bind(this, 'power')} >{translate('power and costs')}</th>
<th style={{ width:'25%' }} className={cn({ active: tab == 'profiles' })} onClick={this._showTab.bind(this, 'profiles')} >{translate('profiles')}</th>
<th style={{ width:'25%' }} className={cn({ active: tab == 'offence' })} onClick={this._showTab.bind(this, 'offence')} >{translate('offence')}</th>
<th style={{ width:'25%' }} className={cn({ active: tab == 'defence' })} onClick={this._showTab.bind(this, 'defence')} >{translate('defence')}</th>
<th style={{ width:'25%' }} className={cn({ active: tab == 'defence' })} onClick={this._showTab.bind(this, 'defence')} >{translate('tab_defence')}</th>
</tr>
</thead>
</table>

View File

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

View File

@@ -32,7 +32,6 @@ function bandText(val, index, wattScale) {
* Renders the SVG to simulate in-game power bands
*/
export default class PowerBands extends TranslatedComponent {
static propTypes = {
bands: PropTypes.array.isRequired,
available: PropTypes.number.isRequired,

View File

@@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import Ship from '../shipyard/Ship';
import { Ships } from 'coriolis-data/dist';
import { Rocket } from './SvgIcons';
import Persist from '../stores/Persist';

View File

@@ -52,6 +52,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const sgMetrics = Calc.shieldMetrics(ship, pips.sys);
const shipBoost = canBoost ? Calc.calcBoost(ship) : 'No Boost';
const restingHeat = Math.sqrt(((ship.standard[0].m.pgen * ship.standard[0].m.eff) / ship.heatCapacity) / 0.2);
const armourMetrics = Calc.armourMetrics(ship);
let shieldColour = 'blue';
if (shieldGenerator && shieldGenerator.m.grp === 'psg') {
@@ -78,13 +79,14 @@ export default class ShipSummaryTable extends TranslatedComponent {
<th rowSpan={2}>{translate('TTD')}</th>
{/* <th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th> */}
<th rowSpan={2}>{translate('cargo')}</th>
<th rowSpan={2}>{translate('pax')}</th>
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'passenger capacity', { cap: 0 })} onMouseLeave={hide}>{translate('pax')}</th>
<th rowSpan={2}>{translate('fuel')}</th>
<th colSpan={3}>{translate('mass')}</th>
<th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
<th rowSpan={2}>{translate('crew')}</th>
<th onMouseEnter={termtip.bind(null, 'mass lock factor', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_BOOST_TIME', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('boost time')}</th>
<th rowSpan={2}>{translate('resting heat (Beta)')}</th>
</tr>
<tr>
<th className='lft'>{translate('max')}</th>
@@ -122,6 +124,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
<td>{ship.crew}</td>
<td>{ship.masslock}</td>
<td>{shipBoost !== 'No Boost' ? formats.time(shipBoost) : 'No Boost'}</td>
<td>{formats.pct(restingHeat)}</td>
</tr>
</tbody>
</table>

View File

@@ -6,7 +6,6 @@ import AvailableModulesMenu from './AvailableModulesMenu';
import ModificationsMenu from './ModificationsMenu';
import { diffDetails } from '../utils/SlotFunctions';
import { wrapCtxMenu } from '../utils/UtilityFunctions';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
/**
* Abstract Slot
@@ -88,7 +87,7 @@ export default class Slot extends TranslatedComponent {
if(event.target.className == 'r') {
this._toggleModifications();
}
this.props.onOpen(event);
this.props.onOpen(event);
}
}
/**

View File

@@ -1,5 +1,4 @@
import React from 'react';
import cn from 'classnames';
import SlotSection from './SlotSection';
import HardpointSlot from './HardpointSlot';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
@@ -8,7 +7,6 @@ import { stopCtxPropagation } from '../utils/UtilityFunctions';
* Utility Slot Section
*/
export default class UtilitySlotSection extends SlotSection {
/**
* Constructor
* @param {Object} props React Component properties
@@ -133,5 +131,4 @@ export default class UtilitySlotSection extends SlotSection {
</ul>
</div>;
}
}

View File

@@ -1,7 +1,7 @@
import TranslatedComponent from './TranslatedComponent';
import React, { PropTypes } from 'react';
import ContainerDimensions from 'react-container-dimensions';
import { BarChart, Bar, XAxis, YAxis } from 'recharts';
import { BarChart, Bar, XAxis, YAxis, LabelList } from 'recharts';
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
const LABEL_COLOUR = '#000000';
@@ -17,7 +17,6 @@ const merge = function(one, two) {
* A vertical bar chart
*/
export default class VerticalBarChart extends TranslatedComponent {
static propTypes = {
data : PropTypes.array.isRequired,
yMax : PropTypes.number
@@ -54,7 +53,9 @@ export default class VerticalBarChart extends TranslatedComponent {
<BarChart width={width} height={width * ASPECT} data={this.props.data} margin={{ top: 5, right: 5, left: 5, bottom: 5 }}>
<XAxis interval={0} fontSize='0.8em' stroke={AXIS_COLOUR} dataKey='label' />
<YAxis interval={'preserveStart'} tickCount={11} fontSize='0.8em' stroke={AXIS_COLOUR} type='number' domain={[0, localMax]}/>
<Bar dataKey='value' label={<ValueLabel />} fill={CORIOLIS_COLOURS[0]} isAnimationActive={false} onMouseOver={this._termtip} onMouseOut={tooltip.bind(null, null)}/>
<Bar dataKey='value' fill={CORIOLIS_COLOURS[0]} isAnimationActive={false} onMouseOver={this._termtip} onMouseOut={tooltip.bind(null, null)}>
<LabelList dataKey='value' position='insideTop'/>
</Bar>
</BarChart>
</div>
)}
@@ -77,29 +78,3 @@ export default class VerticalBarChart extends TranslatedComponent {
}
}
}
/**
* A label that displays the value within the bar of the chart
*/
class ValueLabel extends React.Component {
static propTypes = {
x: PropTypes.number,
y: PropTypes.number,
payload: PropTypes.object,
value: PropTypes.number
};
/**
* Render offence
* @return {React.Component} contents
*/
render() {
const { x, y, payload, value } = this.props;
const em = value < 1000 ? '1em' : value < 1000 ? '0.8em' : '0.7em';
return (
<text x={x} y={y} fill="#000000" textAnchor="middle" dy={20} style={{ fontSize: em }}>{value}</text>
);
}
};

View File

@@ -1,13 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import { nameComparator } from '../utils/SlotFunctions';
import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
import * as Calc from '../shipyard/Calculations';
import Module from '../shipyard/Module';
const DAMAGE_DEALT_COLORS = ['#FFFFFF', '#FF0000', '#00FF00', '#7777FF', '#FFFF00', '#FF00FF', '#00FFFF', '#777777'];

View File

@@ -7,8 +7,12 @@ import * as IT from './it';
import * as RU from './ru';
import * as PL from './pl';
import * as PT from './pt';
import * as CN from './cn';
import * as d3 from 'd3';
const owofy = require("owofy");
let fallbackTerms = EN.terms;
/**
@@ -27,6 +31,7 @@ export function getLanguage(langCode) {
case 'ru': lang = RU; break;
case 'pl': lang = PL; break;
case 'pt': lang = PT; break;
case 'cn': lang = CN; break;
default:
lang = EN;
}
@@ -37,7 +42,14 @@ export function getLanguage(langCode) {
const round = function(x, n) { const ten_n = Math.pow(10,n); return Math.round(x * ten_n) / ten_n; };
if(lang === EN) {
translate = (t, x) => { return currentTerms[t + '_' + x] || currentTerms[t] || t; };
translate = (t, x) => {
if (currentTerms[t + '_' + x]) {
return owofy(currentTerms[t + '_' + x])
} else if (currentTerms[t]) {
return owofy(currentTerms[t])
}
return t;
};
} else {
translate = (t, x) => { return currentTerms[t + '_' + x] || currentTerms[t] || fallbackTerms[t + '_' + x] || fallbackTerms[t] || t; };
}
@@ -94,5 +106,6 @@ export const Languages = {
fr: 'Français',
ru: 'ру́сский',
pl: 'polski',
pt: 'português'
pt: 'português',
cn: '中文'
};

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

@@ -0,0 +1,16 @@
export const formats = {
decimal: '.',
thousands: ',',
grouping: [3],
currency: ['¥', ''],
dateTime: '%a %b %e %X %Y',
date: '%Y年%m月%d日',
time: '%H:%M:%S',
periods: ['AM', 'PM'],
days: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
shortDays: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
shortMonths: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
};
export { default as terms } from './cn.json';

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
import 'babel-polyfill';
import '@babel/polyfill';
import React from 'react';
import { render } from 'react-dom';
import '../less/app.less';

View File

@@ -6,7 +6,6 @@ import { CoriolisLogo, GitHub } from '../components/SvgIcons';
* About Page
*/
export default class AboutPage extends Page {
/**
* Constructor
* @param {Object} props React Component properties
@@ -23,33 +22,112 @@ export default class AboutPage extends Page {
* @return {React.Component} The page contents
*/
renderPage() {
return <div className={'page'} style={{ textAlign: 'left', maxWidth: 800, margin: '0 auto' }}>
<h1><CoriolisLogo style={{ marginRight: '0.4em' }} className='xl'/><span className='warning'>Coriolis EDCD Edition</span></h1>
return (
<div
className={'page'}
style={{ textAlign: 'left', maxWidth: 800, margin: '0 auto' }}
>
<h1>
<CoriolisLogo style={{ marginRight: '0.4em' }} className="xl" />
<span className="warning">Coriolis EDCD Edition</span>
</h1>
<p>This is a clone of the Coriolis project, whose original author is currently unable to maintain it. This clone is maintained by the <a href="http://edcd.github.io/">EDCD community</a>.</p>
<p>To recover your builds, go to <a href='https://coriolis.io/' target='_blank'>https://coriolis.io/</a>, backup your builds (Settings / Backup), copy the text, return here and import (Settings / Import).</p>
<p>The Coriolis project was inspired by <a href='http://www.edshipyard.com/' target='_blank'>E:D Shipyard</a> and, of course, <a href='http://www.elitedangerous.com' target='_blank'>Elite Dangerous</a>. The ultimate goal of Coriolis is to provide rich features to support in-game play and planning while engaging the E:D community to support its development.</p>
<p>Coriolis was created using assets and imagery from Elite: Dangerous, with the permission of Frontier Developments plc, for non-commercial purposes. It is not endorsed by nor reflects the views or opinions of Frontier Developments. A number of assets were sourced from <a href='http://edassets.org' target='_blank'>ED Assets</a></p>
<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>
Coriolis was created using assets and imagery from Elite: Dangerous,
with the permission of Frontier Developments plc, for non-commercial
purposes. It is not endorsed by nor reflects the views or opinions of
Frontier Developments. A number of assets were sourced from{' '}
<a href="http://edassets.org" target="_blank">
ED Assets
</a>
</p>
<a style={{ display: 'block', textDecoration: 'none' }} href='https://github.com/EDCD/coriolis' target='_blank' title='Coriolis Github Project'>
<GitHub style={{ margin: '0.4em' }} className='l fg xl'/>
<h2 style={{ margin: 0, textDecoration: 'none' }}>Github</h2>
github.com/EDCD/coriolis
</a>
<a
style={{ display: 'block', textDecoration: 'none' }}
href="https://github.com/EDCD/coriolis"
target="_blank"
title="Coriolis Github Project"
>
<GitHub style={{ margin: '0.4em' }} className="l fg xl" />
<h2 style={{ margin: 0, textDecoration: 'none' }}>Github</h2>
github.com/EDCD/coriolis
</a>
<p>Coriolis is an open source project. Checkout the list of upcoming features and to-do list on github. Any and all contributions and feedback are welcome. If you encounter any bugs please report them and provide as much detail as possible.</p>
<p>
Coriolis is an open source project. Checkout the list of upcoming
features and to-do list on github. Any and all contributions and
feedback are welcome. If you encounter any bugs please report them and
provide as much detail as possible.
</p>
<h3>Chat</h3>
<p>You can chat to us on our <a href='https://discord.gg/0uwCh6R62aPRjk9w' target='_blank'>EDCD Discord server</a>.</p>
<h3>Chat</h3>
<p>
You can chat to us on our{' '}
<a href="https://discord.gg/0uwCh6R62aPRjk9w" target="_blank">
EDCD Discord server
</a>
.
</p>
<h3>Supporting Coriolis</h3>
<p>Coriolis is an open source project, and I work on it in my free time. I have set up a patreon at <a href='https://www.patreon.com/coriolis_elite'>patreon.com/coriolis_elite</a>, which will be used to keep Coriolis up to date and the servers running.</p>
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
<input type="hidden" name="cmd" value="_s-xclick"/>
<input type="hidden" name="hosted_button_id" value="SJBKT2SWEEU68" />
<input type="image" src="https://www.paypalobjects.com/en_AU/i/btn/btn_donate_SM.gif" border="0" name="submit" alt="PayPal The safer, easier way to pay online!" />
<img alt="" border="0" src="https://www.paypalobjects.com/en_AU/i/scr/pixel.gif" width="1" height="1" />
<h3>Supporting Coriolis</h3>
<p>
Coriolis is an open source project, and I work on it in my free time.
I have set up a patreon at{' '}
<a href="https://www.patreon.com/coriolis_elite">
patreon.com/coriolis_elite
</a>
, which will be used to keep Coriolis up to date and the servers
running.
</p>
<form
action="https://www.paypal.com/cgi-bin/webscr"
method="post"
target="_top"
>
<input type="hidden" name="cmd" value="_s-xclick" />
<input type="hidden" name="hosted_button_id" value="SJBKT2SWEEU68" />
<input
type="image"
src="https://www.paypalobjects.com/en_AU/i/btn/btn_donate_SM.gif"
border="0"
name="submit"
alt="PayPal The safer, easier way to pay online!"
/>
<img
alt=""
border="0"
src="https://www.paypalobjects.com/en_AU/i/scr/pixel.gif"
width="1"
height="1"
/>
</form>
</div>;
</div>
);
}
}

View File

@@ -13,7 +13,14 @@ import ModalCompare from '../components/ModalCompare';
import ModalExport from '../components/ModalExport';
import ModalPermalink from '../components/ModalPermalink';
import ModalImport from '../components/ModalImport';
import { FloppyDisk, Bin, Download, Embed, Rocket, LinkIcon } from '../components/SvgIcons';
import {
FloppyDisk,
Bin,
Download,
Embed,
Rocket,
LinkIcon
} from '../components/SvgIcons';
import ShortenUrl from '../utils/ShortenUrl';
import { comparisonBBCode } from '../utils/BBCode';
const browser = require('detect-browser');
@@ -42,7 +49,6 @@ function sortBy(predicate) {
* Comparison Page
*/
export default class ComparisonPage extends Page {
/**
* Constructor
* @param {Object} props React Component properties
@@ -81,7 +87,13 @@ export default class ComparisonPage extends Page {
for (let shipId in allBuilds) {
for (let buildName in allBuilds[shipId]) {
if (buildName && allBuilds[shipId][buildName]) {
builds.push(this._createBuild(shipId, buildName, allBuilds[shipId][buildName]));
builds.push(
this._createBuild(
shipId,
buildName,
allBuilds[shipId][buildName]
)
);
}
}
}
@@ -89,7 +101,9 @@ export default class ComparisonPage extends Page {
let comparisonData = Persist.getComparison(name);
if (comparisonData) {
defaultFacets = comparisonData.facets;
comparisonData.builds.forEach((b) => builds.push(this._createBuild(b.shipId, b.buildName)));
comparisonData.builds.forEach(b =>
builds.push(this._createBuild(b.shipId, b.buildName))
);
saved = true;
newName = name;
}
@@ -101,7 +115,7 @@ export default class ComparisonPage extends Page {
newName = name = comparisonData.n;
predicate = comparisonData.p;
desc = comparisonData.d;
comparisonData.b.forEach((build) => {
comparisonData.b.forEach(build => {
builds.push(this._createBuild(build.s, build.n, build.c));
if (!importObj[build.s]) {
importObj[build.s] = {};
@@ -118,9 +132,9 @@ export default class ComparisonPage extends Page {
let selectedFacets = new Array(selectedLength);
for (let i = 0; i < ShipFacets.length; i++) {
let facet = Object.assign({ }, ShipFacets[i]);
let facet = Object.assign({}, ShipFacets[i]);
let defaultIndex = defaultFacets.indexOf(facet.i);
if(defaultIndex == -1) {
if (defaultIndex == -1) {
facets.push(facet);
} else {
facet.active = true;
@@ -155,17 +169,18 @@ export default class ComparisonPage extends Page {
_createBuild(id, name, code) {
code = code ? code : Persist.getBuild(id, name); // Retrieve build code if not passed
if (!code) { // No build found
if (!code) {
// No build found
return;
}
let data = Ships[id]; // Get ship properties
let data = Ships[id]; // Get ship properties
let b = new Ship(id, data.properties, data.slots); // Create a new Ship instance
b.buildFrom(code); // Populate components from code
b.buildFrom(code); // Populate components from code
b.buildName = name;
b.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
return b;
};
}
/**
* Update state with the specified sort predicates
@@ -184,13 +199,18 @@ export default class ComparisonPage extends Page {
}
this.setState({ predicate, desc });
};
}
/**
* Show selected builds modal
*/
_selectBuilds() {
this.context.showModal(<ModalCompare onSelect={this._buildsSelected} builds={this.state.builds} />);
this.context.showModal(
<ModalCompare
onSelect={this._buildsSelected}
builds={this.state.builds}
/>
);
}
/**
@@ -224,7 +244,7 @@ export default class ComparisonPage extends Page {
_facetDrag(e) {
this.nodeAfter = false;
this.dragged = e.currentTarget;
let placeholder = this.placeholder = document.createElement('li');
let placeholder = (this.placeholder = document.createElement('li'));
placeholder.style.width = Math.round(this.dragged.offsetWidth) + 'px';
placeholder.className = 'facet-placeholder';
if (!browser || (browser.name !== 'edge' && browser.name !== 'ie')) {
@@ -262,7 +282,7 @@ export default class ComparisonPage extends Page {
_facetDragOver(e) {
e.preventDefault();
if(e.target.className == 'facet-placeholder') {
if (e.target.className == 'facet-placeholder') {
return;
} else if (e.target != e.currentTarget) {
this.over = e.target;
@@ -272,7 +292,7 @@ export default class ComparisonPage extends Page {
let parent = e.target.parentNode;
if (parent == e.currentTarget) {
if(relX > width && this.dragged != e.target) {
if (relX > width && this.dragged != e.target) {
this.nodeAfter = true;
parent.insertBefore(this.placeholder, e.target.nextElementSibling);
} else {
@@ -321,7 +341,7 @@ export default class ComparisonPage extends Page {
let { newName, builds, facets } = this.state;
let selectedFacets = [];
facets.forEach((f) => {
facets.forEach(f => {
if (f.active) {
selectedFacets.unshift(f.i);
}
@@ -348,14 +368,20 @@ export default class ComparisonPage extends Page {
let code = fromComparison(name, builds, selectedFacets, predicate, desc);
let loc = window.location;
return loc.protocol + '//' + loc.host + '/comparison?code=' + encodeURIComponent(code);
return (
loc.protocol +
'//' +
loc.host +
'/comparison?code=' +
encodeURIComponent(code)
);
}
/**
* Generates the long permalink URL
*/
_genPermalink() {
this.context.showModal(<ModalPermalink url={this._buildUrl()}/>);
this.context.showModal(<ModalPermalink url={this._buildUrl()} />);
}
/**
@@ -365,18 +391,25 @@ export default class ComparisonPage extends Page {
let { translate, formats } = this.context.language;
let { facets, builds } = this.state;
let generator = (callback) => {
let generator = callback => {
let url = this._buildUrl();
ShortenUrl(url,
(shortenedUrl) => callback(comparisonBBCode(translate, formats, facets, builds, shortenedUrl)),
(error) => callback(comparisonBBCode(translate, formats, facets, builds, url))
ShortenUrl(
url,
shortenedUrl =>
callback(
comparisonBBCode(translate, formats, facets, builds, shortenedUrl)
),
error =>
callback(comparisonBBCode(translate, formats, facets, builds, url))
);
};
this.context.showModal(<ModalExport
title={translate('forum') + ' BBCode'}
generator={generator}
/>);
this.context.showModal(
<ModalExport
title={translate('forum') + ' BBCode'}
generator={generator}
/>
);
}
/**
@@ -409,7 +442,8 @@ export default class ComparisonPage extends Page {
* @param {Object} nextContext Incoming/Next conext
*/
componentWillReceiveProps(nextProps, nextContext) {
if (this.context.route !== nextContext.route) { // Only reinit state if the route has changed
if (this.context.route !== nextContext.route) {
// Only reinit state if the route has changed
this.setState(this._initState(nextContext));
}
}
@@ -419,7 +453,10 @@ export default class ComparisonPage extends Page {
*/
componentWillMount() {
this.resizeListener = this.context.onWindowResize(this._updateDimensions);
this.persistListener = Persist.addListener('discounts', this._updateDiscounts);
this.persistListener = Persist.addListener(
'discounts',
this._updateDiscounts
);
}
/**
@@ -444,65 +481,132 @@ export default class ComparisonPage extends Page {
renderPage() {
let translate = this.context.language.translate;
let compareHeader;
let { newName, name, saved, builds, facets, predicate, desc, chartWidth } = this.state;
let {
newName,
name,
saved,
builds,
facets,
predicate,
desc,
chartWidth
} = this.state;
if (this.state.compareMode) {
compareHeader = <tr>
<td className='head'>{translate('comparison')}</td>
<td>
<input value={newName} onChange={this._onNameChange} placeholder={translate('Enter Name')} maxLength='50' />
<button onClick={this._save} disabled={!newName || newName == 'all' || saved}>
<FloppyDisk className='lg'/><span className='button-lbl'>{translate('save')}</span>
</button>
<button onClick={this._delete} disabled={name == 'all' || !saved}><Bin className='lg warning'/></button>
<button onClick={this._selectBuilds}>
<Rocket className='lg'/><span className='button-lbl'>{translate('builds')}</span>
</button>
<button className='r' onClick={this._genPermalink} disabled={builds.length == 0}>
<LinkIcon className='lg'/><span className='button-lbl'>{translate('permalink')}</span>
</button>
<button className='r' onClick={this._genBBcode} disabled={builds.length == 0}>
<Embed className='lg'/><span className='button-lbl'>{translate('forum')}</span>
</button>
</td>
</tr>;
compareHeader = (
<tr>
<td className="head">{translate('comparison')}</td>
<td>
<input
value={newName}
onChange={this._onNameChange}
placeholder={translate('Enter Name')}
maxLength="50"
/>
<button
onClick={this._save}
disabled={!newName || newName == 'all' || saved}
>
<FloppyDisk className="lg" />
<span className="button-lbl">{translate('save')}</span>
</button>
<button onClick={this._delete} disabled={name == 'all' || !saved}>
<Bin className="lg warning" />
</button>
<button onClick={this._selectBuilds}>
<Rocket className="lg" />
<span className="button-lbl">{translate('builds')}</span>
</button>
<button
className="r"
onClick={this._genPermalink}
disabled={builds.length == 0}
>
<LinkIcon className="lg" />
<span className="button-lbl">{translate('permalink')}</span>
</button>
<button
className="r"
onClick={this._genBBcode}
disabled={builds.length == 0}
>
<Embed className="lg" />
<span className="button-lbl">{translate('forum')}</span>
</button>
</td>
</tr>
);
} else {
compareHeader = <tr>
<td className='head'>{translate('comparison')}</td>
<td>
<h3>{name}</h3>
<button className='r' onClick={this._import}><Download className='lg'/>{translate('import')}</button>
</td>
</tr>;
compareHeader = (
<tr>
<td className="head">{translate('comparison')}</td>
<td>
<h3>{name}</h3>
<button className="r" onClick={this._import}>
<Download className="lg" />
{translate('import')}
</button>
</td>
</tr>
);
}
return (
<div className={'page'} style={{ fontSize: this.context.sizeRatio + 'em' }}>
<table id='comparison'>
<div
className={'page'}
style={{ fontSize: this.context.sizeRatio + 'em' }}
>
<table id="comparison">
<tbody>
{compareHeader}
<tr key='facets'>
<td className='head'>{translate('compare')}</td>
<tr key="facets">
<td className="head">{translate('compare')}</td>
<td>
<ul id='facet-container' onDragOver={this._facetDragOver}>
{facets.map((f, i) =>
<li key={f.title} data-i={i} draggable='true' onDragStart={this._facetDrag} onDragEnd={this._facetDrop} className={cn('facet', { active: f.active })} onClick={this._toggleFacet.bind(this, f)}>
<ul id="facet-container" onDragOver={this._facetDragOver}>
{facets.map((f, i) => (
<li
key={f.title}
data-i={i}
draggable="true"
onDragStart={this._facetDrag}
onDragEnd={this._facetDrop}
className={cn('facet', { active: f.active })}
onClick={this._toggleFacet.bind(this, f)}
>
{'↔ ' + translate(f.title)}
</li>
)}
))}
</ul>
</td>
</tr>
</tbody>
</table>
<ComparisonTable builds={builds} facets={facets} onSort={this._sortShips} predicate={predicate} desc={desc} />
<ComparisonTable
builds={builds}
facets={facets}
onSort={this._sortShips}
predicate={predicate}
desc={desc}
/>
{!builds.length ?
<div className='chart' ref={node => this.chartRef = node}>{translate('PHRASE_NO_BUILDS')}</div> :
facets.filter((f) => f.active).map((f, i) =>
<div key={f.title} className='chart' ref={ i == 0 ? node => this.chartRef = node : null}>
<h3 className='ptr' onClick={this._sortShips.bind(this, f.props[0])}>{translate(f.title)}</h3>
{!builds.length ? (
<div className="chart" ref={node => (this.chartRef = node)}>
{translate('PHRASE_NO_BUILDS')}
</div>
) : (
facets.filter(f => f.active).map((f, i) => (
<div
key={f.title}
className="chart"
ref={i == 0 ? node => (this.chartRef = node) : null}
>
<h3
className="ptr"
onClick={this._sortShips.bind(this, f.props[0])}
>
{translate(f.title)}
</h3>
<BarChart
width={chartWidth}
data={builds}
@@ -515,8 +619,8 @@ export default class ComparisonPage extends Page {
desc={desc}
/>
</div>
))
)}
</div>
);
}

View File

@@ -5,7 +5,6 @@ import Page from './Page';
* 404 Page
*/
export default class NotFoundPage extends Page {
/**
* Constructor
* @param {Object} props React Component properties
@@ -22,6 +21,10 @@ export default class NotFoundPage extends Page {
* @return {React.Component} The page contents
*/
renderPage() {
return <div className='page' style={{ marginTop: 30 }}>Page <small>{this.context.route.path}</small> Not Found</div>;
return (
<div className="page" style={{ marginTop: 30 }}>
Page <small>{this.context.route.path}</small> Not Found
</div>
);
}
}

View File

@@ -53,7 +53,6 @@ function getTitle(shipName, buildName) {
* The Outfitting Page
*/
export default class OutfittingPage extends Page {
/**
* Constructor
* @param {Object} props React Component properties
@@ -85,22 +84,38 @@ export default class OutfittingPage extends Page {
let shipId = params.ship;
let code = params.code;
let buildName = params.bn;
let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults
let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults
let savedCode = Persist.getBuild(shipId, buildName);
if (!data) {
return { error: { message: 'Ship not found: ' + shipId } };
}
let ship = new Ship(shipId, data.properties, data.slots); // Create a new Ship instance
let ship = new Ship(shipId, data.properties, data.slots); // Create a new Ship instance
if (code) {
ship.buildFrom(code); // Populate modules from serialized 'code' URL param
ship.buildFrom(code); // Populate modules from serialized 'code' URL param
} else {
ship.buildWith(data.defaults); // Populate with default components
ship.buildWith(data.defaults); // Populate with default components
}
this._getTitle = getTitle.bind(this, data.properties.name);
// Obtain ship control from code
const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = this._obtainControlFromCode(ship, code);
const {
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
opponentSys,
opponentEng,
opponentWep,
engagementRange
} = this._obtainControlFromCode(ship, code);
return {
error: null,
title: this._getTitle(buildName),
@@ -114,6 +129,9 @@ export default class OutfittingPage extends Page {
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
@@ -136,7 +154,10 @@ export default class OutfittingPage extends Page {
};
if (Persist.hasBuild(this.state.shipId, stateChanges.newBuildName)) {
stateChanges.savedCode = Persist.getBuild(this.state.shipId, stateChanges.newBuildName);
stateChanges.savedCode = Persist.getBuild(
this.state.shipId,
stateChanges.newBuildName
);
} else {
stateChanges.savedCode = null;
}
@@ -162,7 +183,9 @@ export default class OutfittingPage extends Page {
* @returns {string} the code for this ship
*/
_fullCode(ship, fuel, cargo) {
return `${ship.toString()}.${LZString.compressToBase64(this._controlCode(fuel, cargo))}`;
return `${ship.toString()}.${LZString.compressToBase64(
this._controlCode(fuel, cargo)
)}`;
}
/**
@@ -176,10 +199,17 @@ export default class OutfittingPage extends Page {
let sys = 2;
let eng = 2;
let wep = 2;
let mcSys = 0;
let mcEng = 0;
let mcWep = 0;
let boost = false;
let fuel = ship.fuelCapacity;
let cargo = ship.cargoCapacity;
let opponent = new Ship('eagle', Ships['eagle'].properties, Ships['eagle'].slots).buildWith(Ships['eagle'].defaults);
let opponent = new Ship(
'eagle',
Ships['eagle'].properties,
Ships['eagle'].slots
).buildWith(Ships['eagle'].defaults);
let opponentSys = 2;
let opponentEng = 2;
let opponentWep = 2;
@@ -191,16 +221,25 @@ export default class OutfittingPage extends Page {
const parts = code.split('.');
if (parts.length >= 5) {
// We have control information in the code
const control = LZString.decompressFromBase64(Utils.fromUrlSafe(parts[4])).split('/');
const control = LZString.decompressFromBase64(
Utils.fromUrlSafe(parts[4])
).split('/');
sys = parseFloat(control[0]);
eng = parseFloat(control[1]);
wep = parseFloat(control[2]);
if (sys + eng + wep > 6) {
sys = eng = wep = 2;
}
boost = control[3] == 1 ? true : false;
fuel = parseFloat(control[4]);
cargo = parseInt(control[5]);
fuel = parseFloat(control[4]) || fuel;
cargo = parseInt(control[5]) || cargo;
if (control[6]) {
const shipId = control[6];
opponent = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots);
opponent = new Ship(
shipId,
Ships[shipId].properties,
Ships[shipId].slots
);
if (control[7] && Persist.getBuild(shipId, control[7])) {
// Ship is a particular build
const opponentCode = Persist.getBuild(shipId, control[7]);
@@ -210,10 +249,12 @@ export default class OutfittingPage extends Page {
// Obtain opponent's sys/eng/wep pips from their code
const opponentParts = opponentCode.split('.');
if (opponentParts.length >= 5) {
const opponentControl = LZString.decompressFromBase64(Utils.fromUrlSafe(opponentParts[4])).split('/');
opponentSys = parseFloat(opponentControl[0]);
opponentEng = parseFloat(opponentControl[1]);
opponentWep = parseFloat(opponentControl[2]);
const opponentControl = LZString.decompressFromBase64(
Utils.fromUrlSafe(opponentParts[4])
).split('/');
opponentSys = parseFloat(opponentControl[0]) || opponentSys;
opponentEng = parseFloat(opponentControl[1]) || opponentEng;
opponentWep = parseFloat(opponentControl[2]) || opponentWep;
}
}
} else {
@@ -221,21 +262,50 @@ export default class OutfittingPage extends Page {
opponent.buildWith(Ships[shipId].defaults);
}
}
engagementRange = parseInt(control[8]);
engagementRange = parseInt(control[8]) || engagementRange;
// Multi-crew pips were introduced later on so assign default values
// because those values might not be present.
mcSys = parseInt(control[9]) || mcSys;
mcEng = parseInt(control[10]) || mcEng;
mcWep = parseInt(control[11]) || mcWep;
}
}
return { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange };
return {
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
opponentSys,
opponentEng,
opponentWep,
engagementRange
};
}
/**
* Triggered when pips have been updated
* Triggered when pips have been updated. Multi-crew pips are already included
* in sys, eng and wep but mcSys, mcEng and mcWep make clear where each pip
* comes from.
* @param {number} sys SYS pips
* @param {number} eng ENG pips
* @param {number} wep WEP pips
* @param {number} mcSys SYS pips from multi-crew
* @param {number} mcEng ENG pips from multi-crew
* @param {number} mcWep WEP pips from multi-crew
*/
_pipsUpdated(sys, eng, wep) {
this.setState({ sys, eng, wep }, () => this._updateRouteOnControlChange());
_pipsUpdated(sys, eng, wep, mcSys, mcEng, mcWep) {
this.setState({ sys, eng, wep, mcSys, mcEng, mcWep }, () =>
this._updateRouteOnControlChange()
);
}
/**
@@ -243,7 +313,7 @@ export default class OutfittingPage extends Page {
* @param {boolean} boost true if boosting
*/
_boostUpdated(boost) {
this.setState({ boost }, () => this._updateRouteOnControlChange());
this.setState({ boost }, () => this._updateRouteOnControlChange());
}
/**
@@ -251,7 +321,7 @@ export default class OutfittingPage extends Page {
* @param {number} fuel the amount of fuel, in T
*/
_fuelUpdated(fuel) {
this.setState({ fuel }, () => this._updateRouteOnControlChange());
this.setState({ fuel }, () => this._updateRouteOnControlChange());
}
/**
@@ -259,7 +329,7 @@ export default class OutfittingPage extends Page {
* @param {number} cargo the amount of cargo, in T
*/
_cargoUpdated(cargo) {
this.setState({ cargo }, () => this._updateRouteOnControlChange());
this.setState({ cargo }, () => this._updateRouteOnControlChange());
}
/**
@@ -267,7 +337,9 @@ export default class OutfittingPage extends Page {
* @param {number} engagementRange the engagement range, in m
*/
_engagementRangeUpdated(engagementRange) {
this.setState({ engagementRange }, () => this._updateRouteOnControlChange());
this.setState({ engagementRange }, () =>
this._updateRouteOnControlChange()
);
}
/**
@@ -276,7 +348,11 @@ export default class OutfittingPage extends Page {
* @param {string} opponentBuild the name of the opponent's build
*/
_opponentUpdated(opponent, opponentBuild) {
const opponentShip = new Ship(opponent, Ships[opponent].properties, Ships[opponent].slots);
const opponentShip = new Ship(
opponent,
Ships[opponent].properties,
Ships[opponent].slots
);
let opponentSys = this.state.opponentSys;
let opponentEng = this.state.opponentEng;
let opponentWep = this.state.opponentWep;
@@ -284,9 +360,13 @@ export default class OutfittingPage extends Page {
// Ship is a particular build
opponentShip.buildFrom(Persist.getBuild(opponent, opponentBuild));
// Set pips for opponent
const opponentParts = Persist.getBuild(opponent, opponentBuild).split('.');
const opponentParts = Persist.getBuild(opponent, opponentBuild).split(
'.'
);
if (opponentParts.length >= 5) {
const opponentControl = LZString.decompressFromBase64(Utils.fromUrlSafe(opponentParts[4])).split('/');
const opponentControl = LZString.decompressFromBase64(
Utils.fromUrlSafe(opponentParts[4])
).split('/');
opponentSys = parseFloat(opponentControl[0]);
opponentEng = parseFloat(opponentControl[1]);
opponentWep = parseFloat(opponentControl[2]);
@@ -299,7 +379,16 @@ export default class OutfittingPage extends Page {
opponentWep = 2;
}
this.setState({ opponent: opponentShip, opponentBuild, opponentSys, opponentEng, opponentWep }, () => this._updateRouteOnControlChange());
this.setState(
{
opponent: opponentShip,
opponentBuild,
opponentSys,
opponentEng,
opponentWep
},
() => this._updateRouteOnControlChange()
);
}
/**
@@ -309,8 +398,22 @@ export default class OutfittingPage extends Page {
* @returns {string} The control code
*/
_controlCode(fuel, cargo) {
const { sys, eng, wep, boost, opponent, opponentBuild, engagementRange } = this.state;
const code = `${sys}/${eng}/${wep}/${boost ? 1 : 0}/${fuel || this.state.fuel}/${cargo || this.state.cargo}/${opponent.id}/${opponentBuild ? opponentBuild : ''}/${engagementRange}`;
const {
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
opponent,
opponentBuild,
engagementRange
} = this.state;
const code = `${sys}/${eng}/${wep}/${boost ? 1 : 0}/${fuel ||
this.state.fuel}/${cargo || this.state.cargo}/${opponent.id}/${
opponentBuild ? opponentBuild : ''
}/${engagementRange}/${mcSys}/${mcEng}/${mcWep}`;
return code;
}
@@ -321,27 +424,44 @@ export default class OutfittingPage extends Page {
const { ship, buildName, newBuildName, shipId } = this.state;
// If this is a stock ship the code won't be set, so ensure that we have it
const code = this.state.code || ship.toString();
const code = this.state.code || ship.toString();
Persist.saveBuild(shipId, newBuildName, code);
this._updateRoute(shipId, newBuildName, code);
let opponent, opponentBuild, opponentSys, opponentEng, opponentWep;
if (shipId === this.state.opponent.id && buildName === this.state.opponentBuild) {
if (
shipId === this.state.opponent.id &&
buildName === this.state.opponentBuild
) {
// This is a save of our current opponent build; update it
opponentBuild = newBuildName;
opponent = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots).buildFrom(code);
opponent = new Ship(
shipId,
Ships[shipId].properties,
Ships[shipId].slots
).buildFrom(code);
opponentSys = this.state.sys;
opponentEng = this.state.eng;
opponentWep = this.state.wep;
} else {
opponentBuild = this.state.opponentBuild;
opponent = this.state.opponent;
opponent = this.state.opponent;
opponentSys = this.state.opponentSys;
opponentEng = this.state.opponentEng;
opponentWep = this.state.opponentWep;
}
this.setState({ buildName: newBuildName, code, savedCode: code, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, title: this._getTitle(newBuildName) });
this.setState({
buildName: newBuildName,
code,
savedCode: code,
opponent,
opponentBuild,
opponentSys,
opponentEng,
opponentWep,
title: this._getTitle(newBuildName)
});
}
/**
@@ -353,7 +473,12 @@ export default class OutfittingPage extends Page {
Persist.deleteBuild(shipId, buildName);
Persist.saveBuild(shipId, newBuildName, code);
this._updateRoute(shipId, newBuildName, code);
this.setState({ buildName: newBuildName, code, savedCode: code, opponentBuild: newBuildName });
this.setState({
buildName: newBuildName,
code,
savedCode: code,
opponentBuild: newBuildName
});
}
}
@@ -373,19 +498,38 @@ export default class OutfittingPage extends Page {
ship.buildWith(Ships[shipId].defaults);
// Reset controls
const code = ship.toString();
const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code);
// Update state, and refresh the ship
this.setState({
const {
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
engagementRange
}, () => this._updateRoute(shipId, buildName, code));
} = this._obtainControlFromCode(ship, code);
// Update state, and refresh the ship
this.setState(
{
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
engagementRange
},
() => this._updateRoute(shipId, buildName, code)
);
}
/**
@@ -396,7 +540,10 @@ export default class OutfittingPage extends Page {
Persist.deleteBuild(shipId, buildName);
let opponentBuild;
if (shipId === this.state.opponent.id && buildName === this.state.opponentBuild) {
if (
shipId === this.state.opponent.id &&
buildName === this.state.opponentBuild
) {
// Our current opponent has been deleted; revert to stock
opponentBuild = null;
} else {
@@ -413,11 +560,13 @@ export default class OutfittingPage extends Page {
_exportBuild() {
let translate = this.context.language.translate;
let { buildName, ship } = this.state;
this.context.showModal(<ModalExport
title={(buildName || ship.name) + ' ' + translate('export')}
description={translate('PHRASE_EXPORT_DESC')}
data={toDetailedBuild(buildName, ship, ship.toString())}
/>);
this.context.showModal(
<ModalExport
title={(buildName || ship.name) + ' ' + translate('export')}
description={translate('PHRASE_EXPORT_DESC')}
data={toDetailedBuild(buildName, ship, ship.toString())}
/>
);
}
/**
@@ -430,19 +579,38 @@ export default class OutfittingPage extends Page {
this.state.ship.buildFrom(code);
// Obtain controls from the code
const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code);
// Update state, and refresh the route when complete
this.setState({
const {
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
engagementRange
}, () => this._updateRoute(shipId, buildName, code));
} = this._obtainControlFromCode(ship, code);
// Update state, and refresh the route when complete
this.setState(
{
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
engagementRange
},
() => this._updateRoute(shipId, buildName, code)
);
}
/**
@@ -458,8 +626,14 @@ export default class OutfittingPage extends Page {
}
const code = this._fullCode(ship, fuel, cargo);
// Only update the state if this really has been updated
if (this.state.code != code || this.state.cargo != cargo || this.state.fuel != fuel) {
this.setState({ code, cargo, fuel }, () => this._updateRoute(shipId, buildName, code));
if (
this.state.code != code ||
this.state.cargo != cargo ||
this.state.fuel != fuel
) {
this.setState({ code, cargo, fuel }, () =>
this._updateRoute(shipId, buildName, code)
);
}
}
@@ -479,7 +653,8 @@ export default class OutfittingPage extends Page {
* @param {Object} nextContext Incoming/Next conext
*/
componentWillReceiveProps(nextProps, nextContext) {
if (this.context.route !== nextContext.route) { // Only reinit state if the route has changed
if (this.context.route !== nextContext.route) {
// Only reinit state if the route has changed
this.setState(this._initState(nextProps, nextContext));
}
}
@@ -502,7 +677,7 @@ export default class OutfittingPage extends Page {
* Generates the short URL
*/
_genShortlink() {
this.context.showModal(<ModalPermalink url={window.location.href}/>);
this.context.showModal(<ModalPermalink url={window.location.href} />);
}
/**
@@ -519,7 +694,7 @@ export default class OutfittingPage extends Page {
data.ShipName = ship.id;
data.Ship = ship.id;
console.log(data);
this.context.showModal(<ModalOrbis ship={data}/>);
this.context.showModal(<ModalOrbis ship={data} />);
}
/**
@@ -530,17 +705,23 @@ export default class OutfittingPage extends Page {
const shipId = Ships[ship.id].eddbID;
// Provide unique list of non-PP module EDDB IDs
const modIds = ship.internal.concat(ship.bulkheads, ship.standard, ship.hardpoints).filter(slot => slot !== null && slot.m !== null && !slot.m.pp).map(slot => slot.m.eddbID).filter((v, i, a) => a.indexOf(v) === i);
const modIds = ship.internal
.concat(ship.bulkheads, ship.standard, ship.hardpoints)
.filter(slot => slot !== null && slot.m !== null && !slot.m.pp)
.map(slot => slot.m.eddbID)
.filter((v, i, a) => a.indexOf(v) === i);
// Open up the relevant URL
window.open('https://eddb.io/station?s=' + shipId + '&m=' + modIds.join(','));
window.open(
'https://eddb.io/station?s=' + shipId + '&m=' + modIds.join(',')
);
}
/**
* Generates the shopping list
*/
_genShoppingList() {
this.context.showModal(<ModalShoppingList ship={this.state.ship}/>);
this.context.showModal(<ModalShoppingList ship={this.state.ship} />);
}
/**
@@ -550,8 +731,9 @@ export default class OutfittingPage extends Page {
_keyDown(e) {
// .keyCode will eventually be replaced with .key
switch (e.keyCode) {
case 69: // 'e'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + e
case 69: // 'e'
if (e.ctrlKey || e.metaKey) {
// CTRL/CMD + e
e.preventDefault();
this._exportBuild();
}
@@ -567,7 +749,28 @@ export default class OutfittingPage extends Page {
let state = this.state,
{ language, termtip, tooltip, sizeRatio, onWindowResize } = this.context,
{ translate, units, formats } = language,
{ ship, code, savedCode, buildName, newBuildName, sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = state,
{
ship,
code,
savedCode,
buildName,
newBuildName,
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
opponentSys,
opponentEng,
opponentWep,
engagementRange
} = state,
hide = tooltip.bind(null, null),
menu = this.props.currentMenu,
shipUpdated = this._shipUpdated,
@@ -585,11 +788,15 @@ export default class OutfittingPage extends Page {
const _pStr = `${ship.getPowerEnabledString()}${ship.getPowerPrioritiesString()}`;
const _mStr = ship.getModificationsString();
const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`;
const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}${
ship.ladenMass
}${cargo}${fuel}`;
const internalSlotMarker = `${ship.name}${_iStr}${_pStr}${_mStr}`;
const hardpointsSlotMarker = `${ship.name}${_hStr}${_pStr}${_mStr}`;
const boostMarker = `${ship.canBoost(cargo, fuel)}`;
const shipSummaryMarker = `${ship.name}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`;
const shipSummaryMarker = `${
ship.name
}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`;
const requirements = Ships[ship.id].requirements;
let requirementElements = [];
@@ -601,94 +808,275 @@ export default class OutfittingPage extends Page {
*/
function renderRequirement(className, textKey, tooltipTextKey) {
if (textKey.startsWith('empire') || textKey.startsWith('federation')) {
requirementElements.push(<div key={textKey} className={className} onMouseEnter={termtip.bind(null, tooltipTextKey)} onMouseLeave={hide}><a href={textKey.startsWith('empire') ? 'http://elite-dangerous.wikia.com/wiki/Empire/Ranks' : 'http://elite-dangerous.wikia.com/wiki/Federation/Ranks'} target="_blank" rel="noopener">{translate(textKey)}</a></div>);
requirementElements.push(
<div
key={textKey}
className={className}
onMouseEnter={termtip.bind(null, tooltipTextKey)}
onMouseLeave={hide}
>
<a
href={
textKey.startsWith('empire') ?
'http://elite-dangerous.wikia.com/wiki/Empire/Ranks' :
'http://elite-dangerous.wikia.com/wiki/Federation/Ranks'
}
target="_blank"
rel="noopener"
>
{translate(textKey)}
</a>
</div>
);
} else {
requirementElements.push(<div key={textKey} className={className} onMouseEnter={termtip.bind(null, tooltipTextKey)} onMouseLeave={hide}>{translate(textKey)}</div>);
requirementElements.push(
<div
key={textKey}
className={className}
onMouseEnter={termtip.bind(null, tooltipTextKey)}
onMouseLeave={hide}
>
{translate(textKey)}
</div>
);
}
}
if (requirements) {
requirements.federationRank && renderRequirement('federation', 'federation rank ' + requirements.federationRank, 'federation rank required');
requirements.empireRank && renderRequirement('empire', 'empire rank ' + requirements.empireRank, 'empire rank required');
requirements.horizons && renderRequirement('horizons', 'horizons', 'horizons required');
requirements.horizonsEarlyAdoption && renderRequirement('horizons', 'horizons early adoption', 'horizons early adoption required');
requirements.federationRank &&
renderRequirement(
'federation',
'federation rank ' + requirements.federationRank,
'federation rank required'
);
requirements.empireRank &&
renderRequirement(
'empire',
'empire rank ' + requirements.empireRank,
'empire rank required'
);
requirements.horizons &&
renderRequirement('horizons', 'horizons', 'horizons required');
requirements.horizonsEarlyAdoption &&
renderRequirement(
'horizons',
'horizons early adoption',
'horizons early adoption required'
);
}
return (
<div id='outfit' className={'page'} style={{ fontSize: (sizeRatio * 0.9) + 'em' }}>
<div id='overview'>
<div
id="outfit"
className={'page'}
style={{ fontSize: sizeRatio * 0.9 + 'em' }}
>
<div id="overview">
<h1>{ship.name}</h1>
<div id='requirements'>{requirementElements}</div>
<div id='build'>
<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}>
<FloppyDisk className='lg' />
<div id="requirements">{requirementElements}</div>
<div id="build">
<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}
>
<FloppyDisk className="lg" />
</button>
<button onClick={canRename && this._renameBuild} disabled={!canRename} onMouseOver={termtip.bind(null, 'rename')} onMouseOut={hide}>
<span style={{ textTransform: 'none', fontSize: '1.8em' }}>a|</span>
<button
onClick={canRename && this._renameBuild}
disabled={!canRename}
onMouseOver={termtip.bind(null, 'rename')}
onMouseOut={hide}
>
<span style={{ textTransform: 'none', fontSize: '1.8em' }}>
a|
</span>
</button>
<button onClick={canReload && this._reloadBuild} disabled={!canReload} onMouseOver={termtip.bind(null, 'reload')} onMouseOut={hide}>
<Reload className='lg'/>
<button
onClick={canReload && this._reloadBuild}
disabled={!canReload}
onMouseOver={termtip.bind(null, 'reload')}
onMouseOut={hide}
>
<Reload className="lg" />
</button>
<button className={'danger'} onClick={savedCode && this._deleteBuild} disabled={!savedCode} onMouseOver={termtip.bind(null, 'delete')} onMouseOut={hide}>
<Bin className='lg'/>
<button
className={'danger'}
onClick={savedCode && this._deleteBuild}
disabled={!savedCode}
onMouseOver={termtip.bind(null, 'delete')}
onMouseOut={hide}
>
<Bin className="lg" />
</button>
<button onClick={code && this._resetBuild} disabled={!code} onMouseOver={termtip.bind(null, 'reset')} onMouseOut={hide}>
<Switch className='lg'/>
<button
onClick={code && this._resetBuild}
disabled={!code}
onMouseOver={termtip.bind(null, 'reset')}
onMouseOut={hide}
>
<Switch className="lg" />
</button>
<button onClick={buildName && this._exportBuild} disabled={!buildName} onMouseOver={termtip.bind(null, 'export')} onMouseOut={hide}>
<Download className='lg'/>
<button
onClick={buildName && this._exportBuild}
disabled={!buildName}
onMouseOver={termtip.bind(null, 'export')}
onMouseOut={hide}
>
<Download className="lg" />
</button>
<button onClick={this._eddbShoppingList} onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_LIST')} onMouseOut={hide}>
<ShoppingIcon className='lg' />
<button
onClick={this._eddbShoppingList}
onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_LIST')}
onMouseOut={hide}
>
<ShoppingIcon className="lg" />
</button>
<button onClick={this._genShortlink} onMouseOver={termtip.bind(null, 'shortlink')} onMouseOut={hide}>
<LinkIcon className='lg' />
<button
onClick={this._genShortlink}
onMouseOver={termtip.bind(null, 'shortlink')}
onMouseOut={hide}
>
<LinkIcon className="lg" />
</button>
<button onClick={this._genOrbis} onMouseOver={termtip.bind(null, 'PHASE_UPLOAD_ORBIS')} onMouseOut={hide}>
<OrbisIcon className='lg' />
<button
onClick={this._genOrbis}
onMouseOver={termtip.bind(null, 'PHASE_UPLOAD_ORBIS')}
onMouseOut={hide}
>
<OrbisIcon className="lg" />
</button>
<button onClick={this._genShoppingList} onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_MATS')} onMouseOut={hide}>
<MatIcon className='lg' />
<button
onClick={this._genShoppingList}
onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_MATS')}
onMouseOut={hide}
>
<MatIcon className="lg" />
</button>
</div>
</div>
{/* Main tables */}
<ShipSummaryTable ship={ship} fuel={fuel} cargo={cargo} marker={shipSummaryMarker} pips={{ sys: this.state.sys, wep: this.state.wep, eng: this.state.eng }} />
<StandardSlotSection ship={ship} fuel={fuel} cargo={cargo} code={standardSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} sectionMenuRefs={this._sectionMenuRefs}/>
<InternalSlotSection ship={ship} code={internalSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} sectionMenuRefs={this._sectionMenuRefs}/>
<HardpointSlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} sectionMenuRefs={this._sectionMenuRefs}/>
<UtilitySlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} sectionMenuRefs={this._sectionMenuRefs}/>
<ShipSummaryTable
ship={ship}
fuel={fuel}
cargo={cargo}
marker={shipSummaryMarker}
pips={{
sys: this.state.sys,
wep: this.state.wep,
eng: this.state.eng
}}
/>
<StandardSlotSection
ship={ship}
fuel={fuel}
cargo={cargo}
code={standardSlotMarker}
onChange={shipUpdated}
onCargoChange={this._cargoUpdated}
onFuelChange={this._fuelUpdated}
currentMenu={menu}
sectionMenuRefs={this._sectionMenuRefs}
/>
<InternalSlotSection
ship={ship}
code={internalSlotMarker}
onChange={shipUpdated}
onCargoChange={this._cargoUpdated}
onFuelChange={this._fuelUpdated}
currentMenu={menu}
sectionMenuRefs={this._sectionMenuRefs}
/>
<HardpointSlotSection
ship={ship}
code={hardpointsSlotMarker}
onChange={shipUpdated}
onCargoChange={this._cargoUpdated}
onFuelChange={this._fuelUpdated}
currentMenu={menu}
sectionMenuRefs={this._sectionMenuRefs}
/>
<UtilitySlotSection
ship={ship}
code={hardpointsSlotMarker}
onChange={shipUpdated}
onCargoChange={this._cargoUpdated}
onFuelChange={this._fuelUpdated}
currentMenu={menu}
sectionMenuRefs={this._sectionMenuRefs}
/>
{/* Control of ship and opponent */}
<div className='group quarter'>
<div className='group half'>
<h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}>{translate('ship control')}</h2>
<div className="group quarter">
<div className="group half">
<h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}>
{translate('ship control')}
</h2>
</div>
<div className='group half'>
<Boost marker={boostMarker} ship={ship} boost={boost} onChange={this._boostUpdated} />
<div className="group half">
<Boost
marker={boostMarker}
ship={ship}
boost={boost}
onChange={this._boostUpdated}
/>
</div>
</div>
<div className='group quarter'>
<Pips sys={sys} eng={eng} wep={wep} onChange={this._pipsUpdated} />
<div className="group quarter">
<Pips
sys={sys}
eng={eng}
wep={wep}
mcSys={mcSys}
mcEng={mcEng}
mcWep={mcWep}
onChange={this._pipsUpdated}
/>
</div>
<div className='group quarter'>
<Fuel fuelCapacity={ship.fuelCapacity} fuel={fuel} onChange={this._fuelUpdated}/>
<div className="group quarter">
<Fuel
fuelCapacity={ship.fuelCapacity}
fuel={fuel}
onChange={this._fuelUpdated}
/>
</div>
<div className='group quarter'>
{ ship.cargoCapacity > 0 ? <Cargo cargoCapacity={ship.cargoCapacity} cargo={cargo} onChange={this._cargoUpdated}/> : null }
<div className="group quarter">
{ship.cargoCapacity > 0 ? (
<Cargo
cargoCapacity={ship.cargoCapacity}
cargo={cargo}
onChange={this._cargoUpdated}
/>
) : null}
</div>
<div className='group half'>
<div className='group quarter'>
<h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}>{translate('opponent')}</h2>
<div className="group half">
<div className="group quarter">
<h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}>
{translate('opponent')}
</h2>
</div>
<div className='group threequarters'>
<ShipPicker ship={opponent.id} build={opponentBuild} onChange={this._opponentUpdated}/>
<div className="group threequarters">
<ShipPicker
ship={opponent.id}
build={opponentBuild}
onChange={this._opponentUpdated}
/>
</div>
</div>
<div className='group half'>
<EngagementRange ship={ship} engagementRange={engagementRange} onChange={this._engagementRangeUpdated}/>
<div className="group half">
<EngagementRange
ship={ship}
engagementRange={engagementRange}
onChange={this._engagementRangeUpdated}
/>
</div>
{/* Tabbed subpages */}

View File

@@ -70,6 +70,13 @@ export default class Page extends React.Component {
document.title = this.state.title || 'Coriolis';
}
/**
* Update the window title upon mount
*/
componentDidMount() {
document.title = this.state.title || 'Coriolis';
}
/**
* Updates the title upon change
* @param {Object} newProps Incoming properties

View File

@@ -6,6 +6,7 @@ import Ship from '../shipyard/Ship';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import { SizeMap } from '../shipyard/Constants';
import Link from '../components/Link';
const owofy = require("owofy");
/**
* Counts the hardpoints by class/size
@@ -22,9 +23,11 @@ function countHp(slot) {
*/
function countInt(slot) {
let crEligible = !slot.eligible || slot.eligible.cr;
this.int[slot.maxClass - 1]++; // Subtract 1 since there is no Class 0 Internal compartment
this.int[slot.maxClass - 1]++; // Subtract 1 since there is no Class 0 Internal compartment
this.intCount++;
this.maxCargo += crEligible ? ModuleUtils.findInternal('cr', slot.maxClass, 'E').cargo : 0;
this.maxCargo += crEligible ?
ModuleUtils.findInternal('cr', slot.maxClass, 'E').cargo :
0;
// if no eligiblity, then assume pce
let passSlotType = null;
@@ -42,7 +45,9 @@ function countInt(slot) {
passSlotType = 'pcq';
passSlotRating = 'B';
}
let passengerBay = passSlotType ? ModuleUtils.findMaxInternal(passSlotType, slot.maxClass, passSlotRating) : null;
let passengerBay = passSlotType ?
ModuleUtils.findMaxInternal(passSlotType, slot.maxClass, passSlotRating) :
null;
this.maxPassengers += passengerBay ? passengerBay.passengers : 0;
}
@@ -57,23 +62,27 @@ function shipSummary(shipId, shipData) {
id: shipId,
hpCount: 0,
intCount: 0,
beta: shipData.beta,
maxCargo: 0,
maxPassengers: 0,
hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge
int: [0, 0, 0, 0, 0, 0, 0, 0], // Sizes 1 - 8
standard: shipData.slots.standard,
agility: shipData.properties.pitch + shipData.properties.yaw + shipData.properties.roll
agility:
shipData.properties.pitch +
shipData.properties.yaw +
shipData.properties.roll
};
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.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.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
// Best thrusters
let th;
@@ -97,7 +106,6 @@ function shipSummary(shipId, shipData) {
* The Shipyard summary page
*/
export default class ShipyardPage extends Page {
static cachedShipSummaries = null;
/**
@@ -145,12 +153,15 @@ export default class ShipyardPage extends Page {
shipPredicateIndex = undefined;
}
if (this.state.shipPredicate == shipPredicate && this.state.shipPredicateIndex == shipPredicateIndex) {
if (
this.state.shipPredicate == shipPredicate &&
this.state.shipPredicateIndex == shipPredicateIndex
) {
shipDesc = !shipDesc;
}
this.setState({ shipPredicate, shipDesc, shipPredicateIndex });
};
}
/**
* Generate the table row summary for the ship
@@ -159,56 +170,59 @@ export default class ShipyardPage extends Page {
* @param {Object} u Localized unit map
* @param {Function} fInt Localized integer formatter
* @param {Function} fRound Localized round formatter
* @param {Boolean} highlight Should this row be highlighted
* @return {React.Component} Table Row
*/
_shipRowElement(s, translate, u, fInt, fRound, highlight) {
_shipRowElement(s, translate, u, fInt, fRound) {
let noTouch = this.context.noTouch;
return <tr
return (
<tr
key={s.id}
style={{ height: '1.5em' }}
className={cn({ highlighted: noTouch && this.state.shipId === s.id, alt: highlight })}
style={{ height: "1.5em" }}
className={cn({
highlighted: noTouch && this.state.shipId === s.id
})}
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
>
<td className='ri'>{s.manufacturer}</td>
<td className='ri'>{fInt(s.retailCost)}</td>
<td className='ri cap'>{translate(SizeMap[s.class])}</td>
<td className='ri'>{fInt(s.crew)}</td>
<td className='ri'>{s.masslock}</td>
<td className='ri'>{fInt(s.agility)}</td>
<td className='ri'>{fInt(s.hardness)}</td>
<td className='ri'>{fInt(s.hullMass)}</td>
<td className='ri'>{fInt(s.speed)}</td>
<td className='ri'>{fInt(s.boost)}</td>
<td className='ri'>{fInt(s.baseArmour)}</td>
<td className='ri'>{fInt(s.baseShieldStrength)}</td>
<td className='ri'>{fInt(s.topSpeed)}</td>
<td className='ri'>{fInt(s.topBoost)}</td>
<td className='ri'>{fRound(s.maxJumpRange)}</td>
<td className='ri'>{fInt(s.maxCargo)}</td>
<td className='ri'>{fInt(s.maxPassengers)}</td>
<td className='cn'>{s.standard[0]}</td>
<td className='cn'>{s.standard[1]}</td>
<td className='cn'>{s.standard[2]}</td>
<td className='cn'>{s.standard[3]}</td>
<td className='cn'>{s.standard[4]}</td>
<td className='cn'>{s.standard[5]}</td>
<td className='cn'>{s.standard[6]}</td>
<td className={cn({ disabled: !s.hp[1] })}>{s.hp[1]}</td>
<td className={cn({ disabled: !s.hp[2] })}>{s.hp[2]}</td>
<td className={cn({ disabled: !s.hp[3] })}>{s.hp[3]}</td>
<td className={cn({ disabled: !s.hp[4] })}>{s.hp[4]}</td>
<td className={cn({ disabled: !s.hp[0] })}>{s.hp[0]}</td>
<td className={cn({ disabled: !s.int[0] })}>{s.int[0]}</td>
<td className={cn({ disabled: !s.int[1] })}>{s.int[1]}</td>
<td className={cn({ disabled: !s.int[2] })}>{s.int[2]}</td>
<td className={cn({ disabled: !s.int[3] })}>{s.int[3]}</td>
<td className={cn({ disabled: !s.int[4] })}>{s.int[4]}</td>
<td className={cn({ disabled: !s.int[5] })}>{s.int[5]}</td>
<td className={cn({ disabled: !s.int[6] })}>{s.int[6]}</td>
<td className={cn({ disabled: !s.int[7] })}>{s.int[7]}</td>
</tr>;
<td className="ri">{owofy(s.manufacturer)}</td>
<td className="ri">{fInt(s.retailCost)}</td>
<td className="ri cap">{translate(SizeMap[s.class])}</td>
<td className="ri">{fInt(s.crew)}</td>
<td className="ri">{s.masslock}</td>
<td className="ri">{fInt(s.agility)}</td>
<td className="ri">{fInt(s.hardness)}</td>
<td className="ri">{fInt(s.hullMass)}</td>
<td className="ri">{fInt(s.speed)}</td>
<td className="ri">{fInt(s.boost)}</td>
<td className="ri">{fInt(s.baseArmour)}</td>
<td className="ri">{fInt(s.baseShieldStrength)}</td>
<td className="ri">{fInt(s.topSpeed)}</td>
<td className="ri">{fInt(s.topBoost)}</td>
<td className="ri">{fRound(s.maxJumpRange)}</td>
<td className="ri">{fInt(s.maxCargo)}</td>
<td className="ri">{fInt(s.maxPassengers)}</td>
<td className="cn">{s.standard[0]}</td>
<td className="cn">{s.standard[1]}</td>
<td className="cn">{s.standard[2]}</td>
<td className="cn">{s.standard[3]}</td>
<td className="cn">{s.standard[4]}</td>
<td className="cn">{s.standard[5]}</td>
<td className="cn">{s.standard[6]}</td>
<td className={cn({ disabled: !s.hp[1] })}>{s.hp[1]}</td>
<td className={cn({ disabled: !s.hp[2] })}>{s.hp[2]}</td>
<td className={cn({ disabled: !s.hp[3] })}>{s.hp[3]}</td>
<td className={cn({ disabled: !s.hp[4] })}>{s.hp[4]}</td>
<td className={cn({ disabled: !s.hp[0] })}>{s.hp[0]}</td>
<td className={cn({ disabled: !s.int[0] })}>{s.int[0]}</td>
<td className={cn({ disabled: !s.int[1] })}>{s.int[1]}</td>
<td className={cn({ disabled: !s.int[2] })}>{s.int[2]}</td>
<td className={cn({ disabled: !s.int[3] })}>{s.int[3]}</td>
<td className={cn({ disabled: !s.int[4] })}>{s.int[4]}</td>
<td className={cn({ disabled: !s.int[5] })}>{s.int[5]}</td>
<td className={cn({ disabled: !s.int[6] })}>{s.int[6]}</td>
<td className={cn({ disabled: !s.int[7] })}>{s.int[7]}</td>
</tr>
);
}
/**
@@ -222,7 +236,8 @@ export default class ShipyardPage extends Page {
let fInt = formats.int;
let fRound = formats.round;
let { shipSummaries, shipPredicate, shipPredicateIndex } = this.state;
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}
@@ -239,7 +254,8 @@ export default class ShipyardPage extends Page {
// Sort shipsOverview
shipSummaries.sort((a, b) => {
let valA = a[shipPredicate], valB = b[shipPredicate];
let valA = a[shipPredicate],
valB = b[shipPredicate];
if (shipPredicateIndex != undefined) {
valA = valA[shipPredicateIndex];
@@ -252,7 +268,7 @@ export default class ShipyardPage extends Page {
valB = val;
}
if(valA == valB) {
if (valA == valB) {
if (a.name > b.name) {
return 1;
} else {
@@ -269,47 +285,54 @@ export default class ShipyardPage extends Page {
let shipRows = new Array(shipSummaries.length);
let detailRows = new Array(shipSummaries.length);
let lastShipSortValue = null;
let backgroundHighlight = false;
for (let s of shipSummaries) {
let shipSortValue = s[shipPredicate];
if(shipPredicateIndex != undefined) {
shipSortValue = shipSortValue[shipPredicateIndex];
}
if(shipSortValue != lastShipSortValue) {
backgroundHighlight = !backgroundHighlight;
lastShipSortValue = shipSortValue;
}
detailRows[i] = this._shipRowElement(s, translate, units, fInt, formats.f1, backgroundHighlight);
detailRows[i] = this._shipRowElement(
s,
translate,
units,
fInt,
formats.f1,
);
shipRows[i] = (
<tr
key={i}
style={{ height: '1.5em' }}
className={cn({ highlighted: noTouch && this.state.shipId === s.id, alt: backgroundHighlight })}
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>
<td className="le">
<Link href={'/outfit/' + s.id}>{owofy(s.name)} {s.beta === true ? '(Beta)' : null}</Link>
</td>
</tr>
);
i++;
}
return (
<div className='page' style={{ fontSize: sizeRatio + 'em' }}>
<div style={{ whiteSpace: 'nowrap', margin: '0 auto', fontSize: '0.8em', position: 'relative', display: 'inline-block', maxWidth: '100%' }}>
<div className="page" style={{ fontSize: sizeRatio + 'em' }}>
<div
style={{
whiteSpace: 'nowrap',
margin: '0 auto',
fontSize: '0.8em',
position: 'relative',
display: 'inline-block',
maxWidth: '100%'
}}
>
<table style={{ width: '12em', position: 'absolute', zIndex: 1 }}>
<thead>
<tr>
<th className='le rgt'>&nbsp;</th>
<th className="le rgt">&nbsp;</th>
</tr>
<tr className='main'>
<th className='sortable le rgt' onClick={sortShips('name')}>{translate('ship')}</th>
<tr className="main">
<th className="sortable le rgt" onClick={sortShips('name')}>
{translate('ship')}
</th>
</tr>
<tr>
<th className='le rgt invisible'>{units['m/s']}</th>
<th className="le rgt invisible">{units['m/s']}</th>
</tr>
</thead>
<tbody onMouseLeave={this._highlightShip.bind(this, null)}>
@@ -317,80 +340,262 @@ export default class ShipyardPage extends Page {
</tbody>
</table>
<div style={{ overflowX: 'scroll', maxWidth: '100%' }}>
<table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }}>
<thead>
<tr className='main'>
<th rowSpan={3} className='sortable' onClick={sortShips('manufacturer')}>{translate('manufacturer')}</th>
<th>&nbsp;</th>
<th rowSpan={3} className='sortable' onClick={sortShips('class')}>{translate('size')}</th>
<th rowSpan={3} className='sortable' onClick={sortShips('crew')}>{translate('crew')}</th>
<th rowSpan={3} className='sortable' onMouseEnter={termtip.bind(null, 'mass lock factor')} onMouseLeave={hide} onClick={sortShips('masslock')} >{translate('MLF')}</th>
<th rowSpan={3} className='sortable' onClick={sortShips('agility')}>{translate('agility')}</th>
<th rowSpan={3} className='sortable' onMouseEnter={termtip.bind(null, 'hardness')} onMouseLeave={hide} onClick={sortShips('hardness')}>{translate('hrd')}</th>
<th>&nbsp;</th>
<th colSpan={4}>{translate('base')}</th>
<th colSpan={5}>{translate('max')}</th>
<th className='lft' colSpan={7}></th>
<th className='lft' colSpan={5}></th>
<th className='lft' colSpan={8}></th>
</tr>
<tr>
<th className='sortable lft' onClick={sortShips('retailCost')}>{translate('cost')}</th>
<th className='sortable lft' onClick={sortShips('hullMass')}>{translate('hull')}</th>
<th className='sortable lft' onClick={sortShips('speed')}>{translate('speed')}</th>
<th className='sortable' onClick={sortShips('boost')}>{translate('boost')}</th>
<th className='sortable' onClick={sortShips('baseArmour')}>{translate('armour')}</th>
<th className='sortable' onClick={sortShips('baseShieldStrength')}>{translate('shields')}</th>
<table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }}>
<thead>
<tr className="main">
<th
rowSpan={3}
className="sortable"
onClick={sortShips('manufacturer')}
>
{translate('manufacturer')}
</th>
<th>&nbsp;</th>
<th
rowSpan={3}
className="sortable"
onClick={sortShips('class')}
>
{translate('size')}
</th>
<th
rowSpan={3}
className="sortable"
onClick={sortShips('crew')}
>
{translate('crew')}
</th>
<th
rowSpan={3}
className="sortable"
onMouseEnter={termtip.bind(null, 'mass lock factor')}
onMouseLeave={hide}
onClick={sortShips('masslock')}
>
{translate('MLF')}
</th>
<th
rowSpan={3}
className="sortable"
onClick={sortShips('agility')}
>
{translate('agility')}
</th>
<th
rowSpan={3}
className="sortable"
onMouseEnter={termtip.bind(null, 'hardness')}
onMouseLeave={hide}
onClick={sortShips('hardness')}
>
{translate('hrd')}
</th>
<th>&nbsp;</th>
<th colSpan={4}>{translate('base')}</th>
<th colSpan={5}>{translate('max')}</th>
<th className="lft" colSpan={7} />
<th className="lft" colSpan={5} />
<th className="lft" colSpan={8} />
</tr>
<tr>
<th
className="sortable lft"
onClick={sortShips('retailCost')}
>
{translate('cost')}
</th>
<th className="sortable lft" onClick={sortShips('hullMass')}>
{translate('hull')}
</th>
<th className="sortable lft" onClick={sortShips('speed')}>
{translate('speed')}
</th>
<th className="sortable" onClick={sortShips('boost')}>
{translate('boost')}
</th>
<th className="sortable" onClick={sortShips('baseArmour')}>
{translate('armour')}
</th>
<th
className="sortable"
onClick={sortShips('baseShieldStrength')}
>
{translate('shields')}
</th>
<th className='sortable lft' onClick={sortShips('topSpeed')}>{translate('speed')}</th>
<th className='sortable' onClick={sortShips('topBoost')}>{translate('boost')}</th>
<th className='sortable' onClick={sortShips('maxJumpRange')}>{translate('jump')}</th>
<th className='sortable' onClick={sortShips('maxCargo')}>{translate('cargo')}</th>
<th className='sortable' onClick={sortShips('maxPassengers')}>{translate('pax')}</th>
<th className="sortable lft" onClick={sortShips('topSpeed')}>
{translate('speed')}
</th>
<th className="sortable" onClick={sortShips('topBoost')}>
{translate('boost')}
</th>
<th className="sortable" onClick={sortShips('maxJumpRange')}>
{translate('jump')}
</th>
<th className="sortable" onClick={sortShips('maxCargo')}>
{translate('cargo')}
</th>
<th className="sortable" onClick={sortShips('maxPassengers')} onMouseEnter={termtip.bind(null, 'passenger capacity')}
onMouseLeave={hide}>
{translate('pax')}
</th>
<th className="lft" colSpan={7}>
{translate('core module classes')}
</th>
<th
colSpan={5}
className="sortable lft"
onClick={sortShips('hpCount')}
>
{translate('hardpoints')}
</th>
<th
colSpan={8}
className="sortable lft"
onClick={sortShips('intCount')}
>
{translate('internal compartments')}
</th>
</tr>
<tr>
<th
className="sortable lft"
onClick={sortShips('retailCost')}
>
{units.CR}
</th>
<th className="sortable lft" onClick={sortShips('hullMass')}>
{units.T}
</th>
<th className="sortable lft" onClick={sortShips('speed')}>
{units['m/s']}
</th>
<th className="sortable" onClick={sortShips('boost')}>
{units['m/s']}
</th>
<th>&nbsp;</th>
<th
className="sortable"
onClick={sortShips('baseShieldStrength')}
>
{units.MJ}
</th>
<th className="sortable lft" onClick={sortShips('topSpeed')}>
{units['m/s']}
</th>
<th className="sortable" onClick={sortShips('topBoost')}>
{units['m/s']}
</th>
<th className="sortable" onClick={sortShips('maxJumpRange')}>
{units.LY}
</th>
<th className="sortable" onClick={sortShips('maxCargo')}>
{units.T}
</th>
<th>&nbsp;</th>
<th
className="sortable lft"
onMouseEnter={termtip.bind(null, 'power plant')}
onMouseLeave={hide}
onClick={sortShips('standard', 0)}
>
{'pp'}
</th>
<th
className="sortable"
onMouseEnter={termtip.bind(null, 'thrusters')}
onMouseLeave={hide}
onClick={sortShips('standard', 1)}
>
{'th'}
</th>
<th
className="sortable"
onMouseEnter={termtip.bind(null, 'frame shift drive')}
onMouseLeave={hide}
onClick={sortShips('standard', 2)}
>
{'fsd'}
</th>
<th
className="sortable"
onMouseEnter={termtip.bind(null, 'life support')}
onMouseLeave={hide}
onClick={sortShips('standard', 3)}
>
{'ls'}
</th>
<th
className="sortable"
onMouseEnter={termtip.bind(null, 'power distriubtor')}
onMouseLeave={hide}
onClick={sortShips('standard', 4)}
>
{'pd'}
</th>
<th
className="sortable"
onMouseEnter={termtip.bind(null, 'sensors')}
onMouseLeave={hide}
onClick={sortShips('standard', 5)}
>
{'s'}
</th>
<th
className="sortable"
onMouseEnter={termtip.bind(null, 'fuel tank')}
onMouseLeave={hide}
onClick={sortShips('standard', 6)}
>
{'ft'}
</th>
<th className="sortable lft" onClick={sortShips('hp', 1)}>
{translate('S')}
</th>
<th className="sortable" onClick={sortShips('hp', 2)}>
{translate('M')}
</th>
<th className="sortable" onClick={sortShips('hp', 3)}>
{translate('L')}
</th>
<th className="sortable" onClick={sortShips('hp', 4)}>
{translate('H')}
</th>
<th className="sortable" onClick={sortShips('hp', 0)}>
{translate('U')}
</th>
<th className='lft' colSpan={7}>{translate('core module classes')}</th>
<th colSpan={5} className='sortable lft' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th>
<th colSpan={8} className='sortable lft' onClick={sortShips('intCount')}>{translate('internal compartments')}</th>
</tr>
<tr>
<th className='sortable lft' onClick={sortShips('retailCost')}>{units.CR}</th>
<th className='sortable lft' onClick={sortShips('hullMass')}>{units.T}</th>
<th className='sortable lft' onClick={sortShips('speed')}>{units['m/s']}</th>
<th className='sortable' onClick={sortShips('boost')}>{units['m/s']}</th>
<th>&nbsp;</th>
<th className='sortable' onClick={sortShips('baseShieldStrength')}>{units.MJ}</th>
<th className='sortable lft' onClick={sortShips('topSpeed')}>{units['m/s']}</th>
<th className='sortable' onClick={sortShips('topBoost')}>{units['m/s']}</th>
<th className='sortable' onClick={sortShips('maxJumpRange')}>{units.LY}</th>
<th className='sortable' onClick={sortShips('maxCargo')}>{units.T}</th>
<th>&nbsp;</th>
<th className='sortable lft' onMouseEnter={termtip.bind(null, 'power plant')} onMouseLeave={hide} onClick={sortShips('standard', 0)}>{'pp'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'thrusters')} onMouseLeave={hide} onClick={sortShips('standard', 1)}>{'th'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'frame shift drive')} onMouseLeave={hide} onClick={sortShips('standard', 2)}>{'fsd'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'life support')} onMouseLeave={hide} onClick={sortShips('standard', 3)}>{'ls'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'power distriubtor')} onMouseLeave={hide} onClick={sortShips('standard', 4)}>{'pd'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'sensors')} onMouseLeave={hide} onClick={sortShips('standard', 5)}>{'s'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'fuel tank')} onMouseLeave={hide} onClick={sortShips('standard', 6)}>{'ft'}</th>
<th className='sortable lft' onClick={sortShips('hp',1)}>{translate('S')}</th>
<th className='sortable' onClick={sortShips('hp', 2)}>{translate('M')}</th>
<th className='sortable' onClick={sortShips('hp', 3)}>{translate('L')}</th>
<th className='sortable' onClick={sortShips('hp', 4)}>{translate('H')}</th>
<th className='sortable' onClick={sortShips('hp', 0)}>{translate('U')}</th>
<th className="sortable lft" onClick={sortShips('int', 0)}>
1
</th>
<th className="sortable" onClick={sortShips('int', 1)}>
2
</th>
<th className="sortable" onClick={sortShips('int', 2)}>
3
</th>
<th className="sortable" onClick={sortShips('int', 3)}>
4
</th>
<th className="sortable" onClick={sortShips('int', 4)}>
5
</th>
<th className="sortable" onClick={sortShips('int', 5)}>
6
</th>
<th className="sortable" onClick={sortShips('int', 6)}>
7
</th>
<th className="sortable" onClick={sortShips('int', 7)}>
8
</th>
</tr>
</thead>
<tbody onMouseLeave={this._highlightShip.bind(this, null)}>
{detailRows}
</tbody>
</table>
<th className='sortable lft' onClick={sortShips('int', 0)} >1</th>
<th className='sortable' onClick={sortShips('int', 1)} >2</th>
<th className='sortable' onClick={sortShips('int', 2)} >3</th>
<th className='sortable' onClick={sortShips('int', 3)} >4</th>
<th className='sortable' onClick={sortShips('int', 4)} >5</th>
<th className='sortable' onClick={sortShips('int', 5)} >6</th>
<th className='sortable' onClick={sortShips('int', 6)} >7</th>
<th className='sortable' onClick={sortShips('int', 7)} >8</th>
</tr>
</thead>
<tbody onMouseLeave={this._highlightShip.bind(this, null)}>
{detailRows}
</tbody>
</table>
</div>
</div>
</div>

View File

@@ -94,6 +94,10 @@ export const ModuleGroupToName = {
gsc: 'Guardian Shard Cannon',
tbem: 'Enzyme Missile Rack',
tbrfl: 'Remote Release Flechette Launcher',
pwa: 'Pulse Wave Analyser',
abl: 'Abrasion Blaster',
scl: 'Seismic Charge Launcher',
sdm: 'Sub-Surface Displacement Missile',
};
let GrpNameToCodeMap = {};

View File

@@ -2,7 +2,6 @@
* Modification - a modification and its value
*/
export default class Modification {
/**
* @param {String} id Unique modification ID
* @param {Number} value Value of the modification
@@ -11,5 +10,4 @@ export default class Modification {
this.id = id;
this.value = value;
}
}

View File

@@ -7,7 +7,6 @@ import { STATS_FORMATTING, SI_PREFIXES } from './StatsFormatting';
* Module - active module in a ship's buildout
*/
export default class Module {
/**
* Construct a new module
* @param {Object} params Module parameters. Either grp/id or template
@@ -42,6 +41,7 @@ export default class Module {
* @return {object} The value of the modification. If it is a numeric value then it is returned as an integer value scaled so that 1.23% == 123
*/
getModValue(name, raw) {
let baseVal = this[name];
let result = this.mods && this.mods[name] ? this.mods[name] : null;
if ((!raw) && this.blueprint && this.blueprint.special) {
@@ -52,13 +52,8 @@ export default class Module {
const modification = Modifications.modifications[name];
const multiplier = modification.type === 'percentage' ? 10000 : 100;
if (name === 'explres' || name === 'kinres' || name === 'thermres' || name === 'causres') {
// Resistance modifications in itself are additive, however their
// special effects are multiplicative. They affect the overall result
// by (special effect resistance) * (damage mult after modification),
// i. e. we need to apply the special effect as a multiplier to the
// overall result and then calculate the difference.
let baseMult = this[name] ? 1 - this[name] : 1;
result = (baseMult - (baseMult - result / multiplier) * (1 - modifierActions[name] / 100)) * multiplier;
// Apply resistance modding mechanisms to special effects subsequently
result = result + modifierActions[name] * (1 - (this[name] + result / multiplier)) * 100;
} else if (modification.method === 'additive') {
result = result + modifierActions[name] * 100;
} else if (modification.method === 'overwrite') {
@@ -76,15 +71,6 @@ export default class Module {
}
}
// Resistance modding for hull reinforcement packages has additional
// diminishing returns implemented. The mod value gets lowered by
// the amount of base resistance the hrp has.
if (!isNaN(result) && this.grp === 'hr' &&
(name === 'kinres' || name === 'thermres' || name === 'explres')) {
let baseRes = this[name];
result = result * (1 - baseRes);
}
// Sanitise the resultant value to 4dp equivalent
return isNaN(result) ? result : Math.round(result);
}
@@ -109,11 +95,11 @@ export default class Module {
// This special effect modifies the value being set, so we need to revert it prior to storing the value
const modification = Modifications.modifications[name];
if (name === 'explres' || name === 'kinres' || name === 'thermres' || name === 'causres') {
// Resistance modifications in itself are additive but their
// experimentals are applied multiplicatively therefor we must handle
// them differently here (cf. documentation in getModValue).
let baseMult = (this[name] ? 1 - this[name] : 1);
value = ((baseMult - value / 10000) / (1 - modifierActions[name] / 100) - baseMult) * -10000;
let res = (this[name] ? this[name] : 0) + value / 10000;
let experimental = modifierActions[name] / 100;
value = (experimental - res) / (experimental - 1) - this[name];
value *= 10000;
// value = ((baseMult - value / 10000) / (1 - modifierActions[name] / 100) - baseMult) * -10000;
} else if (modification.method === 'additive') {
value = value - modifierActions[name];
} else if (modification.method === 'overwrite') {
@@ -178,21 +164,17 @@ export default class Module {
baseValue = 0;
}
modValue = value - baseValue;
if (this.grp === 'hr' &&
(name === 'kinres' || name === 'thermres' || name === 'explres')) {
modValue = modValue / (1 - baseValue);
}
} else if (name === 'shieldboost' || name === 'hullboost') {
modValue = (1 + value) / (1 + baseValue) - 1;
} else { // multiplicative
modValue = value / baseValue - 1;
modValue = baseValue == 0 ? 0 : value / baseValue - 1;
}
if (modification.type === 'percentage') {
modValue = modValue * 10000;
} else if (modification.type === 'numeric' && name !== 'burst' &&
name !== 'burstrof') {
modValue = modValue * 100;
modValue = modValue * 100;
}
this.setModValue(name, modValue, valueIsWithSpecial);
@@ -242,38 +224,38 @@ export default class Module {
const modification = Modifications.modifications[name];
let result = this[name];
if (modification) {
// We store percentages as decimals, so to get them back we need to divide by 10000. Otherwise
// we divide by 100. Both ways we end up with a value with two decimal places
let modValue;
if (modification.type === 'percentage') {
modValue = this.getModValue(name) / 10000;
} else if (modification.type === 'numeric') {
modValue = this.getModValue(name) / 100;
} else {
modValue = this.getModValue(name);
if (modification) {
// We store percentages as decimals, so to get them back we need to divide by 10000. Otherwise
// we divide by 100. Both ways we end up with a value with two decimal places
let modValue;
if (modification.type === 'percentage') {
modValue = this.getModValue(name) / 10000;
} else if (modification.type === 'numeric') {
modValue = this.getModValue(name) / 100;
} else {
modValue = this.getModValue(name);
}
if (modValue) {
if (!result && modification.method === 'additive') {
// If the modification is additive and no value is given by default we
// start at zero
result = 0;
}
if (modValue) {
if (!result && modification.method === 'additive') {
// If the modification is additive and no value is given by default we
// start at zero
result = 0;
}
if (result !== undefined) {
if (modification.method === 'additive') {
result = result + modValue;
} else if (modification.method === 'overwrite') {
result = modValue;
} else if (name === 'shieldboost' || name === 'hullboost') {
result = (1 + result) * (1 + modValue) - 1;
} else {
result = result * (1 + modValue);
}
} else if (name === 'burst' || name === 'burstrof') {
// Burst and burst rate of fire are special, as it can not exist but
// have a modification
result = modValue / 100;
if (result !== undefined) {
if (modification.method === 'additive') {
result = result + modValue;
} else if (modification.method === 'overwrite') {
result = modValue;
} else if (name === 'shieldboost' || name === 'hullboost') {
result = (1 + result) * (1 + modValue) - 1;
} else {
result = result * (1 + modValue);
}
} else if (name === 'burstrof') {
// Burst and burst rate of fire are special, as it can not exist but
// have a modification
result = modValue / 100;
}
}
}
@@ -721,8 +703,7 @@ export default class Module {
let result = 0;
if (this['maxmass']) {
result = this['maxmass'];
// max mass is only modified for non-shield boosters
if (result && modified && this.grp !== 'sg') {
if (result && modified) {
let mult = this.getModValue('optmass') / 10000;
if (mult) { result = result * (1 + mult); }
}
@@ -1092,4 +1073,30 @@ export default class Module {
return this.get('hacktime', modified);
}
/**
* Get the scan range for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
* @return {string} the time for this module
*/
getScanRange(modified = true) {
return this.get('scanrange', modified);
}
/**
* Get the scan angle for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
* @return {string} the time for this module
*/
getScanAngle(modified = true) {
return this.get('scanangle', modified);
}
/**
* Get the max angle for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
* @return {string} the time for this module
*/
getMaxAngle(modified = true) {
return this.get('maxangle', modified);
}
}

View File

@@ -17,7 +17,6 @@ function filter(arr, maxClass, minClass, mass) {
* The available module set for a specific ship
*/
export default class ModuleSet {
/**
* Instantiate the module set
* @param {Object} modules All Modules

View File

@@ -63,7 +63,10 @@ export function standard(type, id) {
if (!isNaN(type)) {
type = StandardArray[type];
}
let s = Modules.standard[type].find(e => e.id == id || (e.class == id.charAt(0) && e.rating == id.charAt(1)));
let s = Modules.standard[type].find(e => e.id === id);
if (!s) {
s = Modules.standard[type].find(e => (e.class == id.charAt(0) && e.rating == id.charAt(1)));
}
if (s) {
s = new Module({ template: s });
}

View File

@@ -85,12 +85,12 @@ export function toDetailedBuild(buildName, ship) {
code = ship.toString();
let data = {
$schema: 'https://coriolis.edcd.io/schemas/ship-loadout/4.json#',
$schema: 'https://coriolis.io/schemas/ship-loadout/4.json#',
name: buildName,
ship: ship.name,
references: [{
name: 'Coriolis.io',
url: 'https://coriolis.edcd.io' + outfitURL(ship.id, code, buildName),
url: 'https://coriolis.io' + outfitURL(ship.id, code, buildName),
code,
shipId: ship.id
}],

View File

@@ -71,7 +71,6 @@ function reduceToIDs(idArray, slot, slotIndex) {
* Ship Model - Encapsulates and models in-game ship behavior
*/
export default class Ship {
/**
* @param {String} id Unique ship Id / Key
* @param {Object} properties Basic ship properties such as name, manufacturer, mass, etc
@@ -416,16 +415,16 @@ export default class Ship {
clearModifications(m) {
m.mods = {};
this.updatePowerGenerated()
.updatePowerUsed()
.recalculateMass()
.updateJumpStats()
.recalculateShield()
.recalculateShieldCells()
.recalculateArmour()
.recalculateDps()
.recalculateEps()
.recalculateHps()
.updateMovement();
.updatePowerUsed()
.recalculateMass()
.updateJumpStats()
.recalculateShield()
.recalculateShieldCells()
.recalculateArmour()
.recalculateDps()
.recalculateEps()
.recalculateHps()
.updateMovement();
}
/**
@@ -506,6 +505,11 @@ export default class Ship {
if (isAbsolute) {
m.setPretty(name, value, sentfromui);
} else {
// Resistance modifiers scale with the base value
if (name == 'kinres' || name == 'thermres' || name == 'causres' || name == 'explres') {
let baseValue = m.get(name, false);
value = (1 - baseValue) * value;
}
m.setModValue(name, value, sentfromui);
}
@@ -696,16 +700,16 @@ export default class Ship {
// Update aggragated stats
if (comps) {
this.updatePowerGenerated()
.updatePowerUsed()
.recalculateMass()
.updateJumpStats()
.recalculateShield()
.recalculateShieldCells()
.recalculateArmour()
.recalculateDps()
.recalculateEps()
.recalculateHps()
.updateMovement();
.updatePowerUsed()
.recalculateMass()
.updateJumpStats()
.recalculateShield()
.recalculateShieldCells()
.recalculateArmour()
.recalculateDps()
.recalculateEps()
.recalculateHps()
.updateMovement();
}
return this.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString();
@@ -1187,28 +1191,28 @@ export default class Ship {
// handle unladen mass
unladenMass += chain(slots)
.map(slot => slot.m ? slot.m.get('mass') : null)
.filter()
.map(mass => mass || 0)
.reduce((sum, mass) => sum + mass)
.value();
// handle fuel capacity
fuelCapacity += chain(slots)
.map(slot => slot.m ? slot.m.get('fuel') : null)
.filter()
.map(fuel => fuel || 0)
.reduce((sum, fuel) => sum + fuel)
.value();
// handle cargo capacity
cargoCapacity += chain(slots)
.map(slot => slot.m ? slot.m.get('cargo') : null)
.filter()
.map(cargo => cargo || 0)
.reduce((sum, cargo) => sum + cargo)
.value();
// handle passenger capacity
// handle passenger capacity
passengerCapacity += chain(slots)
.map(slot => slot.m ? slot.m.get('passengers') : null)
.filter()
.map(passengers => passengers || 0)
.reduce((sum, passengers) => sum + passengers)
.value();
@@ -1304,7 +1308,7 @@ export default class Ship {
let fsd = this.standard[2].m; // Frame Shift Drive;
let { unladenMass, fuelCapacity } = this;
this.unladenRange = this.calcUnladenRange(); // Includes fuel weight for jump
this.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd, this); // Full Tank
this.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd, fuelCapacity, this); // Full Tank
this.ladenRange = this.calcLadenRange(); // Includes full tank and caro
this.unladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity, fsd, fuelCapacity, this);
this.ladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity + this.cargoCapacity, fsd, fuelCapacity, this);
@@ -1502,7 +1506,7 @@ export default class Ship {
} else {
buffer.writeInt32LE(slotMod.value, curpos);
}
// const modification = _.find(Modifications.modifications, function(o) { return o.id === slotMod.id; });
const modification = _.find(Modifications.modifications, function(o) { return o.id === slotMod.id; });
// console.log('ENCODE Slot ' + i + ': ' + modification.name + ' = ' + slotMod.value);
curpos += 4;
}
@@ -1515,6 +1519,7 @@ export default class Ship {
}
this.serialized.modifications = zlib.gzipSync(buffer).toString('base64');
// console.log(this.serialized.modifications)
} else {
this.serialized.modifications = null;
}
@@ -1683,11 +1688,11 @@ export default class Ship {
updated;
this.useBulkhead(0)
.use(standard[2], fsd) // FSD
.use(standard[3], ls) // Life Support
.use(standard[5], s) // Sensors
.use(standard[4], pd) // Power Distributor
.use(standard[6], ft); // Fuel Tank
.use(standard[2], fsd) // FSD
.use(standard[3], ls) // Life Support
.use(standard[5], s) // Sensors
.use(standard[4], pd) // Power Distributor
.use(standard[6], ft); // Fuel Tank
// Turn off nearly everything
if (m.fsdDisabled) this.setSlotEnabled(this.standard[2], false);

View File

@@ -78,5 +78,6 @@ export const STATS_FORMATTING = {
'thermres': { 'format': 'pct' },
'wepcap': { 'format': 'round1', 'unit': 'MJ' },
'weprate': { 'format': 'round1', 'unit': 'MW' },
'jumpboost': { 'format': 'round1', 'unit': 'LY' }
'jumpboost': { 'format': 'round1', 'unit': 'LY' },
'proberadius': { 'format': 'pct1', 'unit': 'pct' },
};

View File

@@ -70,7 +70,6 @@ function _delete(key) {
* export is an instance (see end of this file).
*/
export class Persist extends EventEmitter {
/**
* Create an instance
*/

View File

@@ -19,7 +19,7 @@ export function specialToolTip(translate, blueprint, grp, m, specialName) {
// We also add in any benefits from specials that aren't covered above
if (m.blueprint) {
for (const feature in Modifications.modifierActions[specialName]) {
// if (!blueprint.features[feature] && !m.mods.feature) {
// if (!blueprint.features[feature] && !m.mods.feature) {
const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) {
let symbol = '';
@@ -37,14 +37,14 @@ export function specialToolTip(translate, blueprint, grp, m, specialName) {
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature + '_specialTT'}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'}
style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
<tr key={feature + '_specialTT'}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'}
style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
@@ -54,7 +54,7 @@ export function specialToolTip(translate, blueprint, grp, m, specialName) {
<div>
<table width='100%'>
<tbody>
{effects}
{effects}
</tbody>
</table>
</div>
@@ -215,12 +215,12 @@ export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
<div>
<table width='100%'>
<thead>
<tr>
<td>{translate('feature')}</td>
<td>{translate('worst')}</td>
<tr>
<td>{translate('feature')}</td>
<td>{translate('worst')}</td>
{m ? <td>{translate('current')}</td> : null }
<td>{translate('best')}</td>
</tr>
<td>{translate('best')}</td>
</tr>
</thead>
<tbody>
{effects}
@@ -228,10 +228,10 @@ export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
</table>
{ components ? <table width='100%'>
<thead>
<tr>
<td>{translate('component')}</td>
<td>{translate('amount')}</td>
</tr>
<tr>
<td>{translate('component')}</td>
<td>{translate('amount')}</td>
</tr>
</thead>
<tbody>
{components}
@@ -239,9 +239,9 @@ export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
</table> : null }
{ engineersList ? <table width='100%'>
<thead>
<tr>
<td>{translate('engineers')}</td>
</tr>
<tr>
<td>{translate('engineers')}</td>
</tr>
</thead>
<tbody>
{engineersList}

View File

@@ -30,6 +30,8 @@ export const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
'Hauler': 'hauler',
'Independant_Trader': 'keelback',
'Krait_MkII': 'krait_mkii',
'Mamba': 'mamba',
'Krait_Light': 'krait_phantom',
'Orca': 'orca',
'Python': 'python',
'SideWinder': 'sidewinder',

View File

@@ -147,83 +147,79 @@ export function shipFromLoadoutJSON(json) {
break;
default:
}
for (const module of json.Modules) {
if (module.Slot.toLowerCase().search(/hardpoint/) !== -1) {
// Add hardpoints
let hardpoint;
let hardpointClassNum = -1;
let hardpointSlotNum = -1;
let hardpointArrayNum = 0;
for (let i in shipTemplate.slots.hardpoints) {
if (shipTemplate.slots.hardpoints[i] === hardpointClassNum) {
// Another slot of the same class
hardpointSlotNum++;
} else {
// The first slot of a new class
hardpointClassNum = shipTemplate.slots.hardpoints[i];
hardpointSlotNum = 1;
}
// Now that we know what we're looking for, find it
const hardpointName = HARDPOINT_NUM_TO_CLASS[hardpointClassNum] + 'Hardpoint' + hardpointSlotNum;
const hardpointSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === hardpointName.toLowerCase());
if (!hardpointSlot) {
// This can happen with old imports that don't contain new hardpoints
} else if (!hardpointSlot) {
// No module
} else {
hardpoint = _moduleFromFdName(hardpointSlot.Item);
ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true);
ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On;
ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority;
modsToAdd.push({ coriolisMod: hardpoint, json: hardpointSlot });
}
hardpointArrayNum++;
if (module.Slot.toLowerCase().search(/hardpoint/) !== -1) {
// Add hardpoints
let hardpoint;
let hardpointClassNum = -1;
let hardpointSlotNum = -1;
let hardpointArrayNum = 0;
for (let i in shipTemplate.slots.hardpoints) {
if (shipTemplate.slots.hardpoints[i] === hardpointClassNum) {
// Another slot of the same class
hardpointSlotNum++;
} else {
// The first slot of a new class
hardpointClassNum = shipTemplate.slots.hardpoints[i];
hardpointSlotNum = 1;
}
}
if (module.Slot.toLowerCase().search(/slot\d/) !== -1) {
let internalSlotNum = 1;
let militarySlotNum = 1;
for (let i in shipTemplate.slots.internal) {
if (!shipTemplate.slots.internal.hasOwnProperty(i)) {
continue;
}
const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name = 'military' : false;
// The internal slot might be a standard or a military slot. Military slots have a different naming system
let internalSlot = null;
if (isMilitary) {
const internalName = 'Military0' + militarySlotNum;
// Now that we know what we're looking for, find it
const hardpointName = HARDPOINT_NUM_TO_CLASS[hardpointClassNum] + 'Hardpoint' + hardpointSlotNum;
const hardpointSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === hardpointName.toLowerCase());
if (!hardpointSlot) {
// This can happen with old imports that don't contain new hardpoints
} else if (!hardpointSlot) {
// No module
} else {
hardpoint = _moduleFromFdName(hardpointSlot.Item);
ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true);
ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On;
ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority;
modsToAdd.push({ coriolisMod: hardpoint, json: hardpointSlot });
}
hardpointArrayNum++;
}
}
}
let internalSlotNum = 0;
let militarySlotNum = 1;
for (let i in shipTemplate.slots.internal) {
if (!shipTemplate.slots.internal.hasOwnProperty(i)) {
continue;
}
const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'Military' : false;
// The internal slot might be a standard or a military slot. Military slots have a different naming system
let internalSlot = null;
if (isMilitary) {
const internalName = 'Military0' + militarySlotNum;
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
militarySlotNum++;
} else {
// Slot numbers are not contiguous so handle skips.
for (; internalSlot === null && internalSlotNum < 99; internalSlotNum++) {
// Slot sizes have no relationship to the actual size, either, so check all possibilities
for (let slotsize = 0; slotsize < 9; slotsize++) {
const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + slotsize;
if (json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase())) {
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
militarySlotNum++;
} else {
// Slot numbers are not contiguous so handle skips.
while (internalSlot === null && internalSlotNum < 99) {
// Slot sizes have no relationship to the actual size, either, so check all possibilities
for (let slotsize = 0; slotsize < 9; slotsize++) {
const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '0') + internalSlotNum + '_Size' + slotsize;
if (json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase())) {
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
break;
}
}
internalSlotNum++;
}
}
if (!internalSlot) {
// This can happen with old imports that don't contain new slots
} else {
const internalJson = internalSlot;
const internal = _moduleFromFdName(internalJson.Item);
ship.use(ship.internal[i], internal, true);
ship.internal[i].enabled = internalJson.On === true;
ship.internal[i].priority = internalJson.Priority;
modsToAdd.push({ coriolisMod: internal, json: internalSlot });
break;
}
}
}
}
if (!internalSlot) {
// This can happen with old imports that don't contain new slots
} else {
const internalJson = internalSlot;
const internal = _moduleFromFdName(internalJson.Item);
ship.use(ship.internal[i], internal, true);
ship.internal[i].enabled = internalJson.On === true;
ship.internal[i].priority = internalJson.Priority;
modsToAdd.push({ coriolisMod: internal, json: internalSlot });
}
}
for (const i of modsToAdd) {

View File

@@ -113,7 +113,7 @@ const API_ORBIS = 'https://orbis.zone/api/builds/add';
* @return {Promise<any>} Either a URL or error message.
*/
export function orbisUpload(ship, creds) {
return new Promise(async (resolve, reject) => {
return new Promise(async(resolve, reject) => {
if (window.navigator.onLine) {
try {
agent

View File

@@ -1,7 +1,4 @@
import React from 'react';
import cn from 'classnames';
import Module from '../shipyard/Module';
import { Infinite } from '../components/SvgIcons';
import Persist from '../stores/Persist';
import * as ModuleUtils from '../shipyard/ModuleUtils';
@@ -148,11 +145,11 @@ export function diffDetails(language, m, mm) {
if (m.grp === 'pp') {
let mPowerGeneration = m.pgen || 0;
let mmPowerGeneration = mm ? mm.getPowerGeneration() : 0;
if (mPowerGeneration != mmPowerGeneration) propDiffs.push(<div key='pgen'>{translate('pgen')}: <span className={diffClass(mPowerGeneration, mmPowerGeneration)}>{diff(formats.round, mPowerGeneration, mmPowerGeneration)}{units.MJ}</span></div>);
if (mPowerGeneration != mmPowerGeneration) propDiffs.push(<div key='pgen'>{translate('pgen')}: <span className={diffClass(mPowerGeneration, mmPowerGeneration)}>{diff(formats.round, mPowerGeneration, mmPowerGeneration)}{units.MW}</span></div>);
} else {
let mPowerUsage = m.power || 0;
let mmPowerUsage = mm ? mm.getPowerUsage() || 0 : 0;
if (mPowerUsage != mmPowerUsage) propDiffs.push(<div key='power'>{translate('power')}: <span className={diffClass(mPowerUsage, mmPowerUsage, true)}>{diff(formats.round, mPowerUsage, mmPowerUsage)}{units.MJ}</span></div>);
if (mPowerUsage != mmPowerUsage) propDiffs.push(<div key='power'>{translate('power')}: <span className={diffClass(mPowerUsage, mmPowerUsage, true)}>{diff(formats.round, mPowerUsage, mmPowerUsage)}{units.MW}</span></div>);
}
let mDps = m.damage * (m.rpshot || 1) * (m.rof || 1);

61
src/clippy.css Normal file
View File

@@ -0,0 +1,61 @@
.clippy, .clippy-balloon {
position: fixed;
z-index: 1000;
cursor: pointer;
}
.clippy-balloon {
background: #FFC;
color: black;
padding: 8px;
border: 1px solid black;
border-radius: 5px;
}
.clippy-content {
max-width: 200px;
min-width: 120px;
font-family: "Microsoft Sans", sans-serif;
font-size: 10pt;
}
.clippy-tip {
width: 10px;
height: 16px;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAgCAMAAAAlvKiEAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAlQTFRF///MAAAA////52QwgAAAAAN0Uk5T//8A18oNQQAAAGxJREFUeNqs0kEOwCAIRFHn3//QTUU6xMyyxii+jQosrTPkyPEM6IN3FtzIRk1U4dFeKWQiH6pRRowMVKEmvronEynkwj0uZJgR22+YLopPSo9P34wJSamLSU7lSIWLJU7NkNomNlhqxUeAAQC+TQLZyEuJBwAAAABJRU5ErkJggg==) no-repeat;
position: absolute;
}
.clippy-top-left .clippy-tip {
top: 100%;
margin-top: 0px;
left: 100%;
margin-left: -50px;
}
.clippy-top-right .clippy-tip {
top: 100%;
margin-top: 0px;
left: 0;
margin-left: 50px;
background-position: -10px 0;
}
.clippy-bottom-right .clippy-tip {
top: 0;
margin-top: -16px;
left: 0;
margin-left: 50px;
background-position: -10px -16px;
}
.clippy-bottom-left .clippy-tip {
top: 0;
margin-top: -16px;
left: 100%;
margin-left: -50px;
background-position: 0px -16px;
}

1
src/clippy.min.js vendored Normal file

File diff suppressed because one or more lines are too long

9
src/iframe.html Normal file
View File

@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<script src="xdLocalStoragePostMessageApi.min.js"></script>
</head>
<body>
This is the magical iframe
</body>
</html>

View File

@@ -27,7 +27,7 @@
"density": "4.0"
}
],
"start_url": "https:\/\/edcd.coriolis.io",
"start_url": "https:\/\/coriolis.io",
"display": "standalone",
"orientation": "portrait"
}

View File

@@ -1,16 +1,19 @@
<!DOCTYPE html>
<html manifest="/">
<head>
<html>
<head>
<title>Coriolis EDCD Edition</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[0] %>">
<!-- Standard headers -->
<meta name="description" content="A ship builder, outfitting and comparison tool for Elite Dangerous">
<meta name="description" content="A ship builder, outfitting and comparison
tool for Elite Dangerous">
<meta name="mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta name="viewport" content="width=device-width, initial-scale=1.0,
maximum-scale=1.0, user-scalable=0">
<link rel="manifest" href="/manifest.json">
<link rel="shortcut icon" href=/favicon2.ico>
<link rel="icon" sizes="152x152 192x192" type="image/png" href="/192x192.png">
<link rel="icon" sizes="152x152 192x192" type="image/png"
href="/192x192.png">
<!-- Apple/iOS headers -->
<meta name="apple-mobile-web-app-capable" content="yes">
@@ -26,46 +29,74 @@
<script>
window.CORIOLIS_GAPI_KEY = '<%- htmlWebpackPlugin.options.gapiKey %>';
window.CORIOLIS_VERSION = '<%- htmlWebpackPlugin.options.version %>';
window.CORIOLIS_DATE = '<%- htmlWebpackPlugin.options.date.toISOString().slice(0, 10) %>';
window.BUGSNAG_VERSION = '<%- htmlWebpackPlugin.options.version + '-' + htmlWebpackPlugin.options.date.toISOString() %>';
window.CORIOLIS_DATE = '<%- htmlWebpackPlugin.options.date.toISOString().slice(0, 10) %>';
window.BUGSNAG_VERSION = '<%- htmlWebpackPlugin.options.version + '-' + htmlWebpackPlugin.options.date.toISOString() %>';
</script>
<% if (htmlWebpackPlugin.options.uaTracking) { %>
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', '<%- htmlWebpackPlugin.options.uaTracking %>', 'auto');
ga('send', 'pageview');
</script>
<script async src='https://www.google-analytics.com/analytics.js'></script>
<script>
window.ga = window.ga || function() {(ga.q = ga.q || []).push(arguments);};
ga.l = +new Date;
ga('create', '<%- htmlWebpackPlugin.options.uaTracking %>', 'auto');
ga('send', 'pageview');
</script>
<script async src='https://www.google-analytics.com/analytics.js'></script>
<% } %>
<!-- Piwik -->
<!-- <script type="text/javascript">
var _paq = _paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(["setCookieDomain", "*.coriolis.edcd.io"]);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//stats.isadankme.me/";
_paq.push(['setTrackerUrl', u+'piwik.php']);
_paq.push(['setSiteId', '4']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
})();
</script>-->
<!-- <script type="text/javascript">
var _paq = _paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(["setCookieDomain", "*.coriolis.edcd.io"]);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//stats.isadankme.me/";
_paq.push(['setTrackerUrl', u+'piwik.php']);
_paq.push(['setSiteId', '4']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
})();
</script>-->
<!-- End Piwik Code -->
<!-- Bugsnag -->
<script src="//d2wy8f7a9ursnm.cloudfront.net/v4/bugsnag.min.js"></script>
<script src="//d2wy8f7a9ursnm.cloudfront.net/bugsnag-plugins/v1/bugsnag-react.min.js"></script>
<script>
window.bugsnagClient = bugsnag('ba9fae819372850fb660755341fa6ef5', {appVersion: window.BUGSNAG_VERSION || undefined})
window.Bugsnag = window.bugsnagClient
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js"></script>
<script src="https://d2wy8f7a9ursnm.cloudfront.net/v5.0.0/bugsnag.min.js"></script>
<script src="/clippy.min.js"></script>
<script type="text/javascript">
const advice = ['When all else fails, Put a docking computer in every slot. My name is Clippy.',
'Fly in a stright line and roll, your evasion increases by 200%!',
'Boost!!!',
'If you are running out of power, consider deactivating thrusters!',
'Make sure to set fire-groups for your SCBs!',
'If you find yourself dying often - git gud!',
'Remember, shield boosters increase skill!',
'Remember, B rated drives offer greater speed!',
'Point directly into the jet cone and full throttle for optimal FSD boost.',
'Raxxla can only be found in open play.',
'Try silent running when under focus to drop aggro, and reboot when your opponents lose sight of you!'];
clippy.load('Clippy', function(agent) {
// do anything with the loaded agent
agent.show();
setInterval(() => {
agent.animate();
const toSpeak = advice[Math.random() * advice.length >> 0];
agent.speak(toSpeak);
}, (20000));
setInterval(() => {
agent.animate();
agent.speak('@everyone FDL. :pray:');
}, (69000));
});
</script>
</head>
<body style="background-color:#000;">
<section id="coriolis"></section>
<script src="<%= htmlWebpackPlugin.files.chunks.lib.entry %>" charset="utf-8" crossorigin="anonymous"></script>
<script src="<%= htmlWebpackPlugin.files.chunks.app.entry %>" charset="utf-8" crossorigin="anonymous"></script>
</body>
<script>
window.bugsnagClient = bugsnag('ba9fae819372850fb660755341fa6ef5', { appVersion: window.BUGSNAG_VERSION || undefined });
window.Bugsnag = window.bugsnagClient;
</script>
</head>
<body style="background-color:#000;">
<section id="coriolis"></section>
</body>
</html>

View File

@@ -171,3 +171,18 @@ footer {
text-align: right;
}
}
.announcement-container {
display: flex;
align-items: center;
padding-top: 10px;
justify-content: center;
flex-flow: row wrap;
}
.announcement {
border: 1px @secondary solid;
padding: 10px;
}
@import "../../node_modules/clippy.js/build/clippy.css";

View File

@@ -6,8 +6,8 @@
// Foreground colors
@fg: #CCC;
@muted: #999;
@primary: #FF8C0D; // Light Orange
@secondary: #1FB0FF; // Light blue
@primary: #e64980; // Light Orange
@secondary: #9775fa; // Light blue
@warning: #FF3B00; // Dark Orange
@disabled: #555; // Light grey
@success: #71a052; // Green

View File

@@ -12,6 +12,12 @@
cursor: pointer;
}
// A multi-crew pip
.mc {
stroke: @secondary;
fill: @secondary;
}
// A full pip
.full {
stroke: @primary;

View File

@@ -22,6 +22,15 @@ select {
}
}
.react-fuzzy-search > * {
padding: 0 !important;
color: @primary;
& > input {
border: 1px solid @primary !important;
color: @primary-bg;
}
}
.cmdr-select {
border: 1px solid @primary;
padding: 0.5em 0.5em;

View File

@@ -74,7 +74,7 @@
border-color:#fff;
}
input:disabled {
input.greyed-out {
border-color: #888;
color: #888;
}

View File

@@ -55,7 +55,7 @@ tbody tr {
background-color: @warning-bg;
}
&.alt {
&:nth-child(odd){
background-color: @alt-primary-bg;
}
}

View File

@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "https://coriolis.edcd.io/schemas/ship-loadout/1.json#",
"id": "https://coriolis.io/schemas/ship-loadout/1.json#",
"title": "Ship Loadout",
"type": "object",
"description": "The details for a specific ship build/loadout. DEPRECATED in favor of Version 3",

View File

@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "https://coriolis.edcd.io/schemas/ship-loadout/2.json#",
"id": "https://coriolis.io/schemas/ship-loadout/2.json#",
"title": "Ship Loadout",
"type": "object",
"description": "The details for a specific ship build/loadout. DEPRECATED in favor of Version 3",

View File

@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "https://coriolis.edcd.io/schemas/ship-loadout/3.json#",
"id": "https://coriolis.io/schemas/ship-loadout/3.json#",
"title": "Ship Loadout",
"type": "object",
"description": "The details for a specific ship build/loadout",

View File

@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "https://coriolis.edcd.io/schemas/ship-loadout/4.json#",
"id": "https://coriolis.io/schemas/ship-loadout/4.json#",
"title": "Ship Loadout",
"type": "object",
"description": "The details for a specific ship build/loadout",

View File

@@ -1,9 +1,22 @@
console.log('Hello from sw.js');
if (workbox) {
workbox.skipWaiting();
workbox.clientsClaim();
console.log('Yay! Workbox is loaded 🎉');
workbox.precaching.precacheAndRoute(self.__precacheManifest);
workbox.routing.registerNavigationRoute('/index.html');
workbox.routing.registerRoute(
new RegExp('/(.*?)'),
workbox.strategies.staleWhileRevalidate({
plugins: [
new workbox.cacheableResponse.Plugin({
statuses: [0, 200]
})
]
})
);
workbox.routing.registerRoute(
new RegExp('https://fonts.(?:googleapis|gstatic).com/(.*)'),
workbox.strategies.cacheFirst({
@@ -18,24 +31,7 @@ if (workbox) {
]
})
);
workbox.routing.registerRoute(
/\.(?:png|gif|jpg|jpeg|svg)$/,
workbox.strategies.cacheFirst({
cacheName: 'images',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 Days
})
]
})
);
workbox.routing.registerRoute(
/\.(?:js|css)$/,
workbox.strategies.staleWhileRevalidate({
cacheName: 'static-resources'
})
);
try {
workbox.googleAnalytics.initialize();
} catch (e) {
@@ -44,3 +40,18 @@ if (workbox) {
} else {
console.log('Boo! Workbox didn\'t load 😬');
}
self.addEventListener('message', event => {
if (!event.data) {
return;
}
switch (event.data) {
case 'skipWaiting':
self.skipWaiting();
break;
default:
// NOOP
break;
}
});

View File

@@ -0,0 +1 @@
"use strict";window.XdUtils=window.XdUtils||function(){function a(a,b){var c,d=b||{};for(c in a)a.hasOwnProperty(c)&&(d[c]=a[c]);return d}return{extend:a}}(),function(){function a(a,b){var c=XdUtils.extend(b,l);c.id=a,parent.postMessage(JSON.stringify(c),"*")}function b(b,c){a(b,{key:c,value:localStorage.getItem(c)})}function c(b,c,d){localStorage.setItem(c,d),a(b,{success:localStorage.getItem(c)===d})}function d(b,c){localStorage.removeItem(c),a(b,{})}function e(b,c){a(b,{key:localStorage.key(c)})}function f(b){a(b,{size:JSON.stringify(localStorage).length})}function g(b){a(b,{length:localStorage.length})}function h(b){localStorage.clear(),a(b,{})}function i(a){var i;try{i=JSON.parse(a.data)}catch(a){}i&&i.namespace===k&&("set"===i.action?c(i.id,i.key,i.value):"get"===i.action?b(i.id,i.key):"remove"===i.action?d(i.id,i.key):"key"===i.action?e(i.id,i.key):"size"===i.action?f(i.id):"length"===i.action?g(i.id):"clear"===i.action&&h(i.id))}function j(){var a={namespace:k,id:"iframe-ready"};parent.postMessage(JSON.stringify(a),"*")}var k="cross-domain-local-message",l={namespace:k};window.addEventListener?window.addEventListener("message",i,!1):window.attachEvent("onmessage",i),j()}();

View File

@@ -1,49 +1,42 @@
const path = require('path');
const exec = require('child_process').exec;
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const WebpackNotifierPlugin = require('webpack-notifier');
const pkgJson = require('./package');
const buildDate = new Date();
function CopyDirPlugin(source, destination) {
this.source = source;
this.destination = destination;
}
CopyDirPlugin.prototype.apply = function(compiler) {
compiler.plugin('done', () => {
console.log(compiler.outputPath, this.destination);
exec('cp -r ' + this.source + ' ' + path.join(compiler.outputPath, this.destination));
});
};
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
devtool: 'source-map',
devServer: {
headers: { 'Access-Control-Allow-Origin': '*' }
},
mode: 'development',
entry: {
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']
main: './src/app/index.js',
},
resolve: {
// When requiring, you don't need to add these extensions
extensions: ['.js', '.jsx', '.json', '.less']
},
optimization: {
minimize: false,
usedExports: true
},
output: {
path: path.join(__dirname, 'build'),
filename: 'app.js',
chunkFilename: '[name].bundle.js',
publicPath: '/'
},
plugins: [
new CopyDirPlugin(path.join(__dirname, 'src/.htaccess'), ''),
new webpack.optimize.CommonsChunkPlugin({
name: 'lib',
filename: 'lib.js'
}),
new CopyWebpackPlugin(['src/.htaccess', 'src/iframe.html', 'src/xdLocalStoragePostMessageApi.min.js', 'src/clippy.min.js']),
// new webpack.optimize.CommonsChunkPlugin({
// name: 'lib',
// filename: 'lib.js'
// }),
new HtmlWebpackPlugin({
inject: false,
inject: true,
template: path.join(__dirname, 'src/index.ejs'),
version: pkgJson.version,
date: buildDate,
@@ -61,7 +54,10 @@ module.exports = {
module: {
rules: [
{ test: /\.css$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader' }) },
{ test: /\.less$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader!less-loader' }) },
{
test: /\.less$/,
loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader!less-loader' })
},
{ test: /\.(js|jsx)$/, loaders: ['babel-loader'], include: path.join(__dirname, 'src') },
{ test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
{ test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },

View File

@@ -1,63 +1,44 @@
const path = require('path');
const exec = require('child_process').exec;
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const { InjectManifest } = require('workbox-webpack-plugin');
const { BugsnagSourceMapUploaderPlugin } = require('webpack-bugsnag-plugins');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const { BugsnagSourceMapUploaderPlugin, BugsnagBuildReporterPlugin } = require('webpack-bugsnag-plugins');
const pkgJson = require('./package');
const buildDate = new Date();
function CopyDirPlugin(source, destination) {
this.source = source;
this.destination = destination;
}
CopyDirPlugin.prototype.apply = function(compiler) {
compiler.plugin('done', () => {
console.log(compiler.outputPath, this.destination);
exec('cp -r ' + this.source + ' ' + path.join(compiler.outputPath, this.destination));
});
};
module.exports = {
cache: true,
devtool: 'source-map',
entry: {
app: ['babel-polyfill', path.resolve(__dirname, 'src/app/index')],
lib: ['d3', 'react', 'react-dom', 'classnames', 'fbemitter', 'lz-string']
main: './src/app/index.js'
},
resolve: {
extensions: ['.js', '.jsx', '.json', '.less']
},
output: {
path: path.join(__dirname, 'build'),
filename: '[name].[chunkhash:6].js',
chunkFilename: '[name].[chunkhash:6]',
publicPath: '/'
chunkFilename: '[name].bundle.js',
publicPath: '/',
globalObject: 'this'
},
mode: 'production',
optimization: {
minimize: true,
usedExports: true
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
'screw-ie8': true,
sourceMap: true
}),
new CopyWebpackPlugin(['src/.htaccess', { from: 'src/schemas', to: 'schemas' }, {
from: 'src/images/logo/*',
flatten: true,
to: ''
}, 'src/iframe.html', 'src/xdLocalStoragePostMessageApi.min.js', 'src/clippy.min.js']),
// new webpack.optimize.CommonsChunkPlugin({
// name: 'lib',
// filename: 'lib.[chunkhash:6].js'
// }),
new HtmlWebpackPlugin({
inject: false,
appCache: 'coriolis.appcache',
minify: {
collapseBooleanAttributes: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true,
removeEmptyAttributes: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true
},
inject: true,
template: path.join(__dirname, 'src/index.ejs'),
uaTracking: process.env.CORIOLIS_UA_TRACKING || '',
gapiKey: process.env.CORIOLIS_GAPI_KEY || '',
@@ -65,17 +46,19 @@ module.exports = {
version: pkgJson.version
}),
new ExtractTextPlugin({
filename: '[contenthash:6].css',
filename: '[hash:6].css',
disable: false,
allChunks: true
}),
new BugsnagSourceMapUploaderPlugin({
apiKey: 'ba9fae819372850fb660755341fa6ef5',
appVersion: `${pkgJson.version}-${buildDate.toISOString()}`
}),
new CopyDirPlugin(path.join(__dirname, 'src/schemas'), 'schemas'),
new CopyDirPlugin(path.join(__dirname, 'src/images/logo/*'), ''),
new CopyDirPlugin(path.join(__dirname, 'src/.htaccess'), ''),
// new BugsnagBuildReporterPlugin({
// apiKey: 'ba9fae819372850fb660755341fa6ef5',
// appVersion: `${pkgJson.version}-${buildDate.toISOString()}`
// }, { /* opts */ }),
// new BugsnagSourceMapUploaderPlugin({
// apiKey: 'ba9fae819372850fb660755341fa6ef5',
// overwrite: true,
// appVersion: `${pkgJson.version}-${buildDate.toISOString()}`
// }),
new InjectManifest({
swSrc: './src/sw.js',
importWorkboxFrom: 'cdn',
@@ -85,7 +68,10 @@ module.exports = {
module: {
rules: [
{ test: /\.css$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader' }) },
{ test: /\.less$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader!less-loader' }) },
{
test: /\.less$/,
loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader!less-loader' })
},
{ test: /\.(js|jsx)$/, loader: 'babel-loader?cacheDirectory=true', include: path.join(__dirname, 'src') },
{ test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
{ test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },