more react changes, incomplete

This commit is contained in:
Colin McLeod
2015-11-29 18:44:59 -08:00
parent ed637addb8
commit 79224f4f9a
201 changed files with 3594 additions and 2329 deletions

20
.babelrc Normal file
View File

@@ -0,0 +1,20 @@
{
"stage": 0,
"env": {
"development": {
"plugins": ["react-transform"],
"extra": {
"react-transform": {
"transforms": [{
"transform": "react-transform-hmr",
"imports": ["react"],
"locals": ["module"]
}, {
"transform": "react-transform-catch-errors",
"imports": ["react", "redbox-react"]
}]
}
}
}
}
}

21
.eslintrc Normal file
View File

@@ -0,0 +1,21 @@
{
"ecmaFeatures": {
"jsx": true,
"modules": true
},
"env": {
"browser": true,
"node": true
},
"parser": "babel-eslint",
"rules": {
"quotes": [2, "single"],
"strict": [2, "never"],
"react/jsx-uses-react": 2,
"react/jsx-uses-vars": 2,
"react/react-in-jsx-scope": 2
},
"plugins": [
"react"
]
}

5
.gitignore vendored
View File

@@ -1,10 +1,5 @@
node_modules node_modules
bower_components
bower_components/*
build build
.DS_Store .DS_Store
*.log *.log
app/js/db.js
app/db.json
nginx.pid nginx.pid
template_cache.js

View File

@@ -7,12 +7,10 @@ node_js:
cache: cache:
directories: directories:
- node_modules - node_modules
- bower_components
before_script: before_script:
- npm install -g gulp - npm install -g gulp
- npm install -g bower - npm install -g bower
- bower install
script: script:
- gulp lint - gulp lint
- gulp build-prod - gulp build-prod

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<path d="M4 10v20c0 1.1 0.9 2 2 2h18c1.1 0 2-0.9 2-2v-20h-22zM10 28h-2v-14h2v14zM14 28h-2v-14h2v14zM18 28h-2v-14h2v14zM22 28h-2v-14h2v14z"></path>
<path d="M26.5 4h-6.5v-2.5c0-0.825-0.675-1.5-1.5-1.5h-7c-0.825 0-1.5 0.675-1.5 1.5v2.5h-6.5c-0.825 0-1.5 0.675-1.5 1.5v2.5h26v-2.5c0-0.825-0.675-1.5-1.5-1.5zM18 4h-6v-1.975h6v1.975z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 657 B

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<path d="M11.366 22.564l1.291-1.807-1.414-1.414-1.807 1.291c-0.335-0.187-0.694-0.337-1.071-0.444l-0.365-2.19h-2l-0.365 2.19c-0.377 0.107-0.736 0.256-1.071 0.444l-1.807-1.291-1.414 1.414 1.291 1.807c-0.187 0.335-0.337 0.694-0.443 1.071l-2.19 0.365v2l2.19 0.365c0.107 0.377 0.256 0.736 0.444 1.071l-1.291 1.807 1.414 1.414 1.807-1.291c0.335 0.187 0.694 0.337 1.071 0.444l0.365 2.19h2l0.365-2.19c0.377-0.107 0.736-0.256 1.071-0.444l1.807 1.291 1.414-1.414-1.291-1.807c0.187-0.335 0.337-0.694 0.444-1.071l2.19-0.365v-2l-2.19-0.365c-0.107-0.377-0.256-0.736-0.444-1.071zM7 27c-1.105 0-2-0.895-2-2s0.895-2 2-2 2 0.895 2 2-0.895 2-2 2zM32 12v-2l-2.106-0.383c-0.039-0.251-0.088-0.499-0.148-0.743l1.799-1.159-0.765-1.848-2.092 0.452c-0.132-0.216-0.273-0.426-0.422-0.629l1.219-1.761-1.414-1.414-1.761 1.219c-0.203-0.149-0.413-0.29-0.629-0.422l0.452-2.092-1.848-0.765-1.159 1.799c-0.244-0.059-0.492-0.109-0.743-0.148l-0.383-2.106h-2l-0.383 2.106c-0.251 0.039-0.499 0.088-0.743 0.148l-1.159-1.799-1.848 0.765 0.452 2.092c-0.216 0.132-0.426 0.273-0.629 0.422l-1.761-1.219-1.414 1.414 1.219 1.761c-0.149 0.203-0.29 0.413-0.422 0.629l-2.092-0.452-0.765 1.848 1.799 1.159c-0.059 0.244-0.109 0.492-0.148 0.743l-2.106 0.383v2l2.106 0.383c0.039 0.251 0.088 0.499 0.148 0.743l-1.799 1.159 0.765 1.848 2.092-0.452c0.132 0.216 0.273 0.426 0.422 0.629l-1.219 1.761 1.414 1.414 1.761-1.219c0.203 0.149 0.413 0.29 0.629 0.422l-0.452 2.092 1.848 0.765 1.159-1.799c0.244 0.059 0.492 0.109 0.743 0.148l0.383 2.106h2l0.383-2.106c0.251-0.039 0.499-0.088 0.743-0.148l1.159 1.799 1.848-0.765-0.452-2.092c0.216-0.132 0.426-0.273 0.629-0.422l1.761 1.219 1.414-1.414-1.219-1.761c0.149-0.203 0.29-0.413 0.422-0.629l2.092 0.452 0.765-1.848-1.799-1.159c0.059-0.244 0.109-0.492 0.148-0.743l2.106-0.383zM21 15.35c-2.402 0-4.35-1.948-4.35-4.35s1.948-4.35 4.35-4.35 4.35 1.948 4.35 4.35c0 2.402-1.948 4.35-4.35 4.35z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1,8 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(1,1)">
<path stroke="#ff3b00" transform="rotate(45 15 15)" d="m4,4 l 11,-4 l 11,4 l 4,11 l -4,11 l -11,4 l -11,-4 l -4,-11 l 4,-11 l 22,0 l 0,22 l -22,0 z" stroke-width="1" fill="#000000"/>
<rect height="3" width="10" y="13.5" x="10" stroke-width="1" stroke="#ff3b00" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 404 B

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<path d="M16 18l8-8h-6v-8h-4v8h-6zM23.273 14.727l-2.242 2.242 8.128 3.031-13.158 4.907-13.158-4.907 8.127-3.031-2.242-2.242-8.727 3.273v8l16 6 16-6v-8z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 480 B

View File

@@ -1,28 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" height="400" viewBox="0 0 90 32">
<g>
<path d="M19.1,25.2c0.3,0,0.6,0.1,0.7,0.2c0.2,0.1,0.3,0.3,0.4,0.4c0.1,0.2,0.2,0.4,0.2,0.6v3.3c0,0.3-0.1,0.6-0.2,0.7
c-0.1,0.2-0.3,0.3-0.4,0.3c-0.2,0.1-0.4,0.2-0.6,0.1H3.6c-0.3,0-0.6-0.1-0.7-0.2c-0.2-0.1-0.3-0.3-0.3-0.4
c-0.1-0.2-0.2-0.4-0.1-0.6V10.2c0-0.3,0.1-0.5,0.2-0.7C2.7,9.4,2.9,9.3,3,9.2C3.2,9.1,3.4,9,3.6,9h15.5c0.3,0,0.6,0.1,0.7,0.2
c0.2,0.1,0.3,0.3,0.4,0.4c0.1,0.2,0.2,0.4,0.2,0.6V22c0,0.3-0.1,0.6-0.2,0.7c-0.1,0.2-0.3,0.3-0.4,0.3c-0.2,0.1-0.4,0.2-0.6,0.1
h-6.8v-6.8c0.3-0.2,0.6-0.4,0.8-0.7c0.2-0.3,0.3-0.7,0.3-1c0-0.6-0.2-1.1-0.6-1.4c-0.4-0.4-0.9-0.6-1.4-0.6c-0.5,0-1,0.2-1.4,0.6
c-0.4,0.4-0.6,0.9-0.6,1.4c0,0.8,0.3,1.4,1,1.8v8.7H19.1z"/>
<path d="M24.6,29.7V10.2c0-0.2,0-0.4,0.1-0.6c0.1-0.1,0.2-0.3,0.3-0.4C25.3,9.1,25.5,9,25.8,9h5.5c0.2,0,0.4,0.1,0.6,0.2
c0.1,0.1,0.3,0.2,0.4,0.3c0.1,0.1,0.2,0.4,0.2,0.7v13.2c-0.7,0.4-1,1-1,1.8c0,0.5,0.2,1,0.6,1.4c0.4,0.4,0.9,0.6,1.4,0.6
c0.6,0,1.1-0.2,1.4-0.6c0.4-0.4,0.6-0.9,0.6-1.4c0-0.4-0.1-0.8-0.3-1.1c-0.2-0.3-0.4-0.5-0.8-0.7V2.3c0-0.2,0-0.4,0.1-0.6
c0.1-0.1,0.2-0.3,0.3-0.4C35.2,1.1,35.4,1,35.8,1h5.5c0.2,0,0.4,0.1,0.6,0.2c0.1,0.1,0.3,0.2,0.4,0.4c0.1,0.2,0.2,0.4,0.2,0.7v27.4
c0,0.2-0.1,0.4-0.2,0.6c-0.1,0.1-0.2,0.3-0.4,0.4c-0.2,0.1-0.4,0.2-0.7,0.2H25.8c-0.2,0-0.4,0-0.6-0.1c-0.1-0.1-0.3-0.2-0.4-0.3
C24.7,30.3,24.6,30,24.6,29.7z"/>
<path d="M46.9,29.7V10.2c0-0.2,0-0.4,0.1-0.6c0.1-0.1,0.2-0.3,0.3-0.4C47.5,9.1,47.7,9,48.1,9h5.5c0.2,0,0.4,0.1,0.6,0.2
c0.1,0.1,0.3,0.2,0.4,0.3c0.1,0.1,0.2,0.4,0.2,0.7v13.2c-0.7,0.4-1,1-1,1.8c0,0.5,0.2,1,0.6,1.4c0.4,0.4,0.9,0.6,1.4,0.6
c0.6,0,1.1-0.2,1.4-0.6c0.4-0.4,0.6-0.9,0.6-1.4c0-0.4-0.1-0.8-0.3-1.1c-0.2-0.3-0.4-0.5-0.8-0.7V2.3c0-0.2,0-0.4,0.1-0.6
c0.1-0.1,0.2-0.3,0.3-0.4C57.4,1.1,57.7,1,58,1h5.5c0.2,0,0.4,0.1,0.6,0.2c0.1,0.1,0.3,0.2,0.4,0.4c0.1,0.2,0.2,0.4,0.2,0.7v27.4
c0,0.2-0.1,0.4-0.2,0.6c-0.1,0.1-0.2,0.3-0.4,0.4s-0.4,0.2-0.7,0.2H48.1c-0.2,0-0.4,0-0.6-0.1c-0.1-0.1-0.3-0.2-0.4-0.3
C46.9,30.3,46.9,30,46.9,29.7z"/>
<path d="M87,29.7c0,0.3-0.1,0.6-0.2,0.7c-0.1,0.2-0.3,0.3-0.4,0.3c-0.2,0.1-0.4,0.2-0.6,0.1H70.3c-0.3,0-0.6-0.1-0.7-0.2
s-0.3-0.3-0.3-0.4c-0.1-0.2-0.2-0.4-0.1-0.6V2.3c0-0.3,0.1-0.6,0.2-0.7c0.1-0.2,0.3-0.3,0.4-0.4C69.9,1.1,70.1,1,70.3,1h5.5
c0.3,0,0.6,0.1,0.7,0.2c0.2,0.1,0.3,0.3,0.4,0.4c0.1,0.2,0.2,0.4,0.2,0.6v21.2c-0.7,0.4-1,1-1,1.8c0,0.5,0.2,1,0.6,1.4
c0.4,0.4,0.8,0.6,1.4,0.6c0.6,0,1.1-0.2,1.4-0.6c0.4-0.4,0.6-0.9,0.6-1.4c0-0.4-0.1-0.8-0.3-1.1c-0.2-0.3-0.4-0.5-0.8-0.7V10.2
c0-0.3,0.1-0.5,0.2-0.7c0.1-0.1,0.3-0.3,0.4-0.3C79.8,9.1,80,9,80.2,9h5.5c0.3,0,0.6,0.1,0.7,0.2c0.2,0.1,0.3,0.3,0.4,0.4
C87,9.8,87,10,87,10.2V29.7z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -1,4 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<path d="M18 23l3 3 10-10-10-10-3 3 7 7z"></path>
<path d="M14 9l-3-3-10 10 10 10 3-3-7-7z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 248 B

View File

@@ -1,3 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<path d="M448 128v-16c0-26.4-21.6-48-48-48h-160c-26.4 0-48 21.6-48 48v16h-192v128h192v16c0 26.4 21.6 48 48 48h160c26.4 0 48-21.6 48-48v-16h576v-128h-576zM256 256v-128h128v128h-128zM832 432c0-26.4-21.6-48-48-48h-160c-26.4 0-48 21.6-48 48v16h-576v128h576v16c0 26.4 21.6 48 48 48h160c26.4 0 48-21.6 48-48v-16h192v-128h-192v-16zM640 576v-128h128v128h-128zM448 752c0-26.4-21.6-48-48-48h-160c-26.4 0-48 21.6-48 48v16h-192v128h192v16c0 26.4 21.6 48 48 48h160c26.4 0 48-21.6 48-48v-16h576v-128h-576v-16zM256 896v-128h128v128h-128z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 646 B

View File

@@ -1,3 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<path d="M28 0h-28v32h32v-28l-4-4zM16 4h4v8h-4v-8zM28 28h-24v-24h2v10h18v-10h2.343l1.657 1.657v22.343z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 260 B

View File

@@ -1,3 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<path d="M16 0c-8.837 0-16 7.163-16 16s7.163 16 16 16 16-7.163 16-16-7.163-16-16-16zM9.464 26.067c0.347-0.957 0.536-1.99 0.536-3.067 0-3.886-2.463-7.197-5.913-8.456 0.319-2.654 1.508-5.109 3.427-7.029 2.267-2.266 5.28-3.515 8.485-3.515s6.219 1.248 8.485 3.515c1.92 1.92 3.108 4.375 3.428 7.029-3.45 1.26-5.913 4.57-5.913 8.456 0 1.077 0.189 2.11 0.536 3.067-1.928 1.258-4.18 1.933-6.536 1.933s-4.608-0.675-6.536-1.933zM17.242 20.031c0.434 0.109 0.758 0.503 0.758 0.969v2c0 0.55-0.45 1-1 1h-2c-0.55 0-1-0.45-1-1v-2c0-0.466 0.324-0.86 0.758-0.969l0.742-14.031h1l0.742 14.031z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 688 B

View File

@@ -1,3 +0,0 @@
<svg height="1024" width="1024" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M512 0C229.252 0 0 229.25199999999995 0 512c0 226.251 146.688 418.126 350.155 485.813 25.593 4.686 34.937-11.125 34.937-24.626 0-12.188-0.469-52.562-0.718-95.314-128.708 23.46-161.707-31.541-172.469-60.373-5.525-14.809-30.407-60.249-52.398-72.263-17.988-9.828-43.26-33.237-0.917-33.735 40.434-0.476 69.348 37.308 78.471 52.75 45.938 77.749 119.876 55.627 148.999 42.5 4.654-32.999 17.902-55.627 32.501-68.373-113.657-12.939-233.22-56.875-233.22-253.063 0-55.94 19.968-101.561 52.658-137.404-5.22-12.999-22.844-65.095 5.063-135.563 0 0 42.937-13.749 140.811 52.501 40.811-11.406 84.594-17.031 128.124-17.22 43.499 0.188 87.314 5.874 128.188 17.28 97.689-66.311 140.686-52.501 140.686-52.501 28 70.532 10.375 122.564 5.124 135.499 32.811 35.844 52.626 81.468 52.626 137.404 0 196.686-119.751 240-233.813 252.686 18.439 15.876 34.748 47.001 34.748 94.748 0 68.437-0.686 123.627-0.686 140.501 0 13.625 9.312 29.561 35.25 24.562C877.436 929.998 1024 738.126 1024 512 1024 229.25199999999995 794.748 0 512 0z" />
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<path d="M31.562 25.905l-9.423-9.423c-0.583-0.583-1.538-0.583-2.121 0l-0.707 0.707-5.75-5.75 9.439-9.439h-10l-4.439 4.439-0.439-0.439h-2.121v2.121l0.439 0.439-6.439 6.439 5 5 6.439-6.439 5.75 5.75-0.707 0.707c-0.583 0.583-0.583 1.538 0 2.121l9.423 9.423c0.583 0.583 1.538 0.583 2.121 0l3.535-3.535c0.583-0.583 0.583-1.538 0-2.121z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 659 B

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<path d="M24.5 23.5c-2.003 0-3.887-0.78-5.303-2.197l-3.197-3.196-3.196 3.196c-1.417 1.417-3.3 2.197-5.303 2.197s-3.887-0.78-5.304-2.197c-1.417-1.417-2.197-3.3-2.197-5.303s0.78-3.887 2.197-5.304c1.417-1.417 3.3-2.197 5.304-2.197s3.887 0.78 5.303 2.197l3.196 3.196 3.196-3.196c1.417-1.417 3.3-2.197 5.303-2.197s3.887 0.78 5.303 2.197c1.417 1.417 2.197 3.3 2.197 5.304s-0.78 3.887-2.197 5.303c-1.416 1.417-3.3 2.197-5.303 2.197zM21.304 19.197c0.854 0.853 1.989 1.324 3.196 1.323s2.342-0.47 3.196-1.324c0.854-0.854 1.324-1.989 1.324-3.196s-0.47-2.342-1.324-3.196c-0.854-0.854-1.989-1.324-3.196-1.324s-2.342 0.47-3.196 1.324l-3.196 3.196 3.196 3.197zM7.5 11.48c-1.207 0-2.342 0.47-3.196 1.324s-1.324 1.989-1.324 3.196c0 1.207 0.47 2.342 1.324 3.196s1.989 1.324 3.196 1.324c1.207 0 2.342-0.47 3.196-1.324l3.196-3.196-3.196-3.196c-0.854-0.854-1.989-1.324-3.196-1.324v0z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<path d="M14 9.5c0-0.825 0.675-1.5 1.5-1.5h1c0.825 0 1.5 0.675 1.5 1.5v1c0 0.825-0.675 1.5-1.5 1.5h-1c-0.825 0-1.5-0.675-1.5-1.5v-1z"></path>
<path d="M20 24h-8v-2h2v-6h-2v-2h6v8h2z"></path>
<path d="M16 0c-8.837 0-16 7.163-16 16s7.163 16 16 16 16-7.163 16-16-7.163-16-16-16zM16 29c-7.18 0-13-5.82-13-13s5.82-13 13-13 13 5.82 13 13-5.82 13-13 13z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 675 B

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<path d="M13.757 19.868c-0.416 0-0.832-0.159-1.149-0.476-2.973-2.973-2.973-7.81 0-10.783l6-6c1.44-1.44 3.355-2.233 5.392-2.233s3.951 0.793 5.392 2.233c2.973 2.973 2.973 7.81 0 10.783l-2.743 2.743c-0.635 0.635-1.663 0.635-2.298 0s-0.635-1.663 0-2.298l2.743-2.743c1.706-1.706 1.706-4.481 0-6.187-0.826-0.826-1.925-1.281-3.094-1.281s-2.267 0.455-3.094 1.281l-6 6c-1.706 1.706-1.706 4.481 0 6.187 0.635 0.635 0.635 1.663 0 2.298-0.317 0.317-0.733 0.476-1.149 0.476z"></path>
<path d="M8 31.625c-2.037 0-3.952-0.793-5.392-2.233-2.973-2.973-2.973-7.81 0-10.783l2.743-2.743c0.635-0.635 1.664-0.635 2.298 0s0.635 1.663 0 2.298l-2.743 2.743c-1.706 1.706-1.706 4.481 0 6.187 0.826 0.826 1.925 1.281 3.094 1.281s2.267-0.455 3.094-1.281l6-6c1.706-1.706 1.706-4.481 0-6.187-0.635-0.635-0.635-1.663 0-2.298s1.663-0.635 2.298 0c2.973 2.973 2.973 7.81 0 10.783l-6 6c-1.44 1.44-3.355 2.233-5.392 2.233z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,7 +0,0 @@
<svg width="201" height="201" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<circle fill-opacity="0" r="70" cy="100" cx="100" stroke-width="5" />
<line y2="60" x2="101" y1="0" x1="101" stroke-width="5" />
<line y2="101" x2="200" y1="101" x1="140" stroke-width="5" />
<line y2="101" x2="60" y1="101" x1="0" stroke-width="5" />
<line y2="200" x2="101" y1="140" x1="101" stroke-width="5" />
</svg>

Before

Width:  |  Height:  |  Size: 417 B

View File

@@ -1,4 +0,0 @@
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<ellipse ry="25" rx="95" cy="100" cx="100" fill-opacity="0" stroke-width="5" />
<ellipse ry="95" rx="25" cy="100" cx="100" fill-opacity="0" stroke-width="5" />
</svg>

Before

Width:  |  Height:  |  Size: 258 B

View File

@@ -1,5 +0,0 @@
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<line y2="170" x2="162" y1="170" x1="8" stroke-width="6" />
<path d="m13,138l144,0l0,-50l-27,-40l-90,0l-27,40l0,50z" id="svg_12" fill-opacity="0" stroke-width="6" />
<line y2="91" x2="200" y1="91" x1="159" stroke-width="6" />
</svg>

Before

Width:  |  Height:  |  Size: 326 B

View File

@@ -1,3 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
<path d="M437.020 74.98c-48.353-48.351-112.64-74.98-181.020-74.98s-132.667 26.629-181.020 74.98c-48.351 48.353-74.98 112.64-74.98 181.020s26.629 132.667 74.98 181.020c48.353 48.351 112.64 74.98 181.020 74.98s132.667-26.629 181.020-74.98c48.351-48.353 74.98-112.64 74.98-181.020s-26.629-132.667-74.98-181.020zM448 256c0 41.407-13.177 79.794-35.556 111.19l-267.633-267.634c31.396-22.379 69.782-35.556 111.189-35.556 105.869 0 192 86.131 192 192zM64 256c0-41.407 13.177-79.793 35.556-111.189l267.635 267.634c-31.397 22.378-69.784 35.555-111.191 35.555-105.869 0-192-86.131-192-192z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 697 B

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<path d="M16 3c-3.472 0-6.737 1.352-9.192 3.808s-3.808 5.72-3.808 9.192c0 3.472 1.352 6.737 3.808 9.192s5.72 3.808 9.192 3.808c3.472 0 6.737-1.352 9.192-3.808s3.808-5.72 3.808-9.192c0-3.472-1.352-6.737-3.808-9.192s-5.72-3.808-9.192-3.808zM16 0v0c8.837 0 16 7.163 16 16s-7.163 16-16 16c-8.837 0-16-7.163-16-16s7.163-16 16-16zM14 22h4v4h-4zM14 6h4v12h-4z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 681 B

View File

@@ -1,3 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
<path d="M192 0l-192 256h192l-128 256 448-320h-256l192-192z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 178 B

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<path d="M14 22h4v4h-4zM22 8c1.105 0 2 0.895 2 2v6l-6 4h-4v-2l6-4v-2h-10v-4h12zM16 3c-3.472 0-6.737 1.352-9.192 3.808s-3.808 5.72-3.808 9.192c0 3.472 1.352 6.737 3.808 9.192s5.72 3.808 9.192 3.808c3.472 0 6.737-1.352 9.192-3.808s3.808-5.72 3.808-9.192c0-3.472-1.352-6.737-3.808-9.192s-5.72-3.808-9.192-3.808zM16 0v0c8.837 0 16 7.163 16 16s-7.163 16-16 16c-8.837 0-16-7.163-16-16s7.163-16 16-16z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 723 B

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<path d="M22 2l-10 10h-6l-6 8c0 0 6.357-1.77 10.065-0.94l-10.065 12.94 13.184-10.255c1.839 4.208-1.184 10.255-1.184 10.255l8-6v-6l10-10 2-10-10 2z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 475 B

View File

@@ -1,3 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="33" height="33" viewBox="0 0 33 33">
<path d="M32 12h-12l4.485-4.485c-2.267-2.266-5.28-3.515-8.485-3.515s-6.219 1.248-8.485 3.515c-2.266 2.267-3.515 5.28-3.515 8.485s1.248 6.219 3.515 8.485c2.267 2.266 5.28 3.515 8.485 3.515s6.219-1.248 8.485-3.515c0.189-0.189 0.371-0.384 0.546-0.583l3.010 2.634c-2.933 3.349-7.239 5.464-12.041 5.464-8.837 0-16-7.163-16-16s7.163-16 16-16c4.418 0 8.418 1.791 11.313 4.687l4.687-4.687v12z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 499 B

View File

@@ -1,6 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200">
<g>
<rect x="73.001" y="94.017" width="53.997" height="11.945"/>
<path d="M10.324,185.445l89.217,14.348l0.458,0.077l89.677-14.43L200,99.998l-10.338-89.765L100,0.129L10.34,10.233 L-0.001,99.986L10.324,185.445z M193.206,99.986L100,191.108L6.795,99.986L100,8.868L193.206,99.986z M6.82,107.775l87.583,85.624 l-78.983-12.702L6.82,107.775z M184.583,180.692l-78.992,12.712l87.587-85.634L184.583,180.692z M193.745,92.746L105.26,6.245 l79.339,8.938L193.745,92.746z M15.41,15.185L94.736,6.25L6.255,92.751L15.41,15.185z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 642 B

View File

@@ -1,7 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200">
<g>
<path d="M100.002,200C155.139,200,200,155.142,200,100.001c0-55.143-44.861-100.002-99.998-100.002 C44.86-0.001-0.002,44.857-0.002,100.001C-0.001,155.142,44.86,200,100.002,200z M100.002,5.574 c52.063,0,94.423,42.359,94.423,94.427c0,52.067-42.361,94.422-94.423,94.422c-52.07,0-94.428-42.358-94.428-94.422 C5.574,47.933,47.933,5.574,100.002,5.574z"/>
<path d="M100.002,148.557c26.771,0,48.558-21.783,48.558-48.555c0-26.771-21.786-48.556-48.558-48.556 c-26.777,0-48.557,21.782-48.557,48.556C51.446,126.778,73.225,148.557,100.002,148.557z M100.002,57.015 c23.699,0,42.986,19.283,42.986,42.986c0,23.7-19.282,42.987-42.986,42.987c-23.705,0-42.991-19.282-42.991-42.987 C57.011,76.298,76.302,57.015,100.002,57.015z"/>
<rect x="73.404" y="93.985" width="53.197" height="12.033"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 912 B

View File

@@ -1,6 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200">
<g>
<path d="M100.002,200c55.138,0,99.996-44.861,99.996-100c0-55.141-44.858-100-99.996-100 C44.861,0-0.001,44.857-0.001,100C0,155.139,44.861,200,100.002,200z M100.002,194.424c-35.465,0-66.413-19.663-82.552-48.651 l44.426-23.388c7.704,13.067,21.888,21.884,38.127,21.884c16.054,0,30.096-8.621,37.853-21.446l44.441,23.389 C166.092,174.961,135.282,194.424,100.002,194.424z M100.002,61.306c21.335,0,38.691,17.356,38.691,38.694 c0,21.338-17.364,38.691-38.691,38.691c-21.339,0-38.696-17.354-38.696-38.691C61.307,78.662,78.663,61.306,100.002,61.306z M194.422,100c0,14.802-3.427,28.808-9.521,41.287l-44.447-23.4c2.433-5.477,3.812-11.521,3.812-17.89 c0-23.578-18.539-42.852-41.8-44.145V5.636C153.392,6.956,194.422,48.762,194.422,100z M96.895,5.655v50.233 C73.938,57.491,55.73,76.635,55.73,100c0,6.187,1.286,12.081,3.592,17.434l-44.455,23.402C8.911,128.472,5.571,114.619,5.571,100 C5.577,48.972,46.261,7.297,96.895,5.655z"/>
<rect x="73.403" y="93.983" width="53.196" height="12.032"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,7 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200">
<g>
<path d="M145.137,59.126h4.498v6.995h5.576V46.556h-5.576v6.994h-4.498V16.328h-5.574v57.667h-15.411v14.824h-7.63 v-14.58h-13.044v14.58h-8.295v-14.58H82.138v14.58h-6.573v-14.58H59.072v14.58h-6.573v-14.58H39.458v36.338h13.041V94.391h6.573 v16.186h16.493V94.391h6.573v16.186h13.044V94.391h8.295v16.186h13.044V94.391h7.63v40.457l17.634,17.637h13.185v31.182h5.577 V73.996H145.14v-14.87H145.137z M154.97,146.907h-10.871l-14.376-14.376V79.57h25.247V146.907z"/>
<rect fill="#999999" x="147.703" y="16.328" width="5.572" height="7.345"/>
<rect fill="#999999" x="131.295" y="16.328" width="5.577" height="7.345"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 744 B

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<path d="M0 26h32v4h-32zM4 18h4v6h-4zM10 10h4v14h-4zM16 16h4v8h-4zM22 4h4v20h-4z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 409 B

View File

@@ -1,4 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="33" height="33" viewBox="0 0 33 33">
<path d="M20 4.581v4.249c1.131 0.494 2.172 1.2 3.071 2.099 1.889 1.889 2.929 4.4 2.929 7.071s-1.040 5.182-2.929 7.071c-1.889 1.889-4.4 2.929-7.071 2.929s-5.182-1.040-7.071-2.929c-1.889-1.889-2.929-4.4-2.929-7.071s1.040-5.182 2.929-7.071c0.899-0.899 1.94-1.606 3.071-2.099v-4.249c-5.783 1.721-10 7.077-10 13.419 0 7.732 6.268 14 14 14s14-6.268 14-14c0-6.342-4.217-11.698-10-13.419zM14 0h4v16h-4z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 510 B

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<path d="M14 18h4v-8h6l-8-8-8 8h6zM20 13.5v3.085l9.158 3.415-13.158 4.907-13.158-4.907 9.158-3.415v-3.085l-12 4.5v8l16 6 16-6v-8z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 458 B

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<path d="M16 2.899l13.409 26.726h-26.819l13.409-26.726zM16 0c-0.69 0-1.379 0.465-1.903 1.395l-13.659 27.222c-1.046 1.86-0.156 3.383 1.978 3.383h27.166c2.134 0 3.025-1.522 1.978-3.383h0l-13.659-27.222c-0.523-0.93-1.213-1.395-1.903-1.395v0z"></path>
<path d="M18 26c0 1.105-0.895 2-2 2s-2-0.895-2-2c0-1.105 0.895-2 2-2s2 0.895 2 2z"></path>
<path d="M16 22c-1.105 0-2-0.895-2-2v-6c0-1.105 0.895-2 2-2s2 0.895 2 2v6c0 1.105-0.895 2-2 2z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 762 B

View File

@@ -1,55 +0,0 @@
import { Component } from 'react';
import Router from 'Router';
import ShipyardPage from 'pages/ShipyardPage';
import NotFoundPage from 'pages/NotFoundPage';
import Header from '../components/Header';
class Coriolis extends Component {
constructor(props) {
super(props);
this.setPage = this.setPage.bind(this);
this.state.standAlone = isStandAlone();
window.onerror = errorPage.bind(this);
Router('/', () => this.setPage(<ShipyardPage />));
// Router('/outfitting/:ship', outfitting);
// Router('/outfitting/:ship/:code', outfitting);
// Router('/compare/:name', compare);
// Router('/comparison/:code', comparison);
// Router('/settings', settings);
Router('*', () => this.setPage(null));
if (window.applicationCache) {
// Listen for appcache updated event, present refresh to update view
window.applicationCache.addEventListener('updateready', () => {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
// Browser downloaded a new app cache.
this.setState({appCacheUpdate: true});
}
}, false);
}
Router.start();
console.log('Root page created');
}
setPage(page) {
this.setState({ page: page });
}
onError(msg, scriptUrl, line, col, errObj) {
this.setPage(<div>Some errors occured!!</div>);
}
render() {
return (
<div>
{/* <Header appCacheUpdate={appCacheUpdate} /> */}
{this.state.page || <NotFoundPage />}
</div>
);
}
}
ReactDOM.render(<Coriolis />, document.getElementById('coriolis'));

View File

@@ -1,118 +0,0 @@
import { Component } from 'react';
export default class Header extends Component {
render() {
let openedMenu = this.state.openedMenu;
if (this.props.appCacheUpdate) {
return <div id="app-update" onClick={window.location.reload}>{ 'PHRASE_UPDATE_RDY' | translate }</div>;
}
return (
<header>
<a class="l" ui-sref="shipyard" style="margin-right: 1em;" title="Ships"><svg class="icon xl"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#coriolis"></use></svg></a>
<div class="l menu">
<div class="menu-header" ng-class="{selected: openedMenu=='s'}" ng-click="openMenu($event,'s')">
<svg class="icon warning"><use xlink:href="#rocket"></use></svg><span class="menu-item-label"> {{'ships' | translate}}</span>
</div>
{openedMenu == 's' ? this.getShipsMenu() : null}
</div>
<div class="l menu">
<div class="menu-header" ng-class="{selected: openedMenu=='b', disabled: !bs.hasBuilds}" ng-click="openMenu($event,'b')">
<svg class="icon warning" ng-class="{'warning-disabled': !bs.hasBuilds}"><use xlink:href="#hammer"></use></svg><span class="menu-item-label"> {{'builds' | translate}}</span>
</div>
{openedMenu == 'b' ? this.getBuildsMenu() : null}
</div>
<div class="l menu">
<div class="menu-header" ng-class="{selected: openedMenu=='comp', disabled: !bs.hasBuilds}" ng-click="openMenu($event,'comp')">
<svg class="icon warning" ng-class="{'warning-disabled': !bs.hasBuilds}"><use xlink:href="#stats-bars"></use></svg><span class="menu-item-label"> {{'compare' | translate}}</span>
</div>
{openedMenu == 'comp' ? this.getComparisonsMenu() : null}
</div>
<div class="r menu">
<div class="menu-header" ng-class="{selected: openedMenu=='settings'}" ng-click="openMenu($event,'settings')">
<svg class="icon xl warning"><use xlink:href="#cogs"></use></svg><span class="menu-item-label"> {{'settings' | translate}}</span>
</div>
{openedMenu == 'settings' ? this.getSettingsMenu() : null}
</div>
</header>
);
}
getShipsMenu() {
return (<div class="menu-list dbl no-wrap">
<a class="block" ng-repeat="(shipId,ship) in ships" ui-sref-active="active" ui-sref="outfit({shipId:shipId, code:null, bn:null})">{{::ship.properties.name}}</a>
</div>);
}
getBuildsMenu() {
return (<div class="menu-list" ng-if="openedMenu=='b'" ng-click="$event.stopPropagation();">
<div class="dbl" >
<div><ul ng-repeat="shipId in buildsList">
{{ships[shipId].properties.name}}
<li ng-repeat="(i, name) in cleanedBuildList(shipId)">
<a ui-sref-active="active" class="name" ui-sref="outfit({shipId:shipId, code:allBuilds[shipId][name], bn:name})" ng-bind="name"></a>
</li>
</ul></div>
</div>
</div>);
}
getComparisonsMenu() {
return (<div class="menu-list" ng-if="openedMenu=='comp'" ng-click="$event.stopPropagation();" style="white-space: nowrap;">
<span class="cap" ng-if="!bs.hasComparisons" translate="none created"></span>
<a ng-repeat="(i, name) in allComparisons" ui-sref-active="active" class="block name" ui-sref="compare({name:name})" ng-bind="name"></a>
<hr />
<a ui-sref="compare({name: 'all'})" class="block cap" translate="compare all"></a>
<a ui-sref="compare({name: null})" class="block cap" translate="create new"></a>
</div>);
}
getSettingsMenu() {
return (<div class="menu-list no-wrap cap" ng-if="openedMenu=='settings'" ng-click="$event.stopPropagation();">
<ul>
{{'language' | translate}}
<li><select class="cap" ng-model="language.current" ng-options="langCode as langName for (langCode,langName) in language.opts" ng-change="changeLanguage()"></select></li>
</ul><br>
<ul>
{{'insurance' | translate}}
<li><select class="cap" ng-model="insurance.current" ng-options="ins.name | translate for (i,ins) in insurance.opts" ng-change="updateInsurance()"></select></li>
</ul><br>
<ul>
{{'ship' | translate}} {{'discount' | translate}}
<li><select class="cap" ng-model="discounts.ship" ng-options="i for (i,d) in discounts.opts" ng-change="updateDiscount()"></select></li>
</ul><br>
<ul>
{{'component' | translate}} {{'discount' | translate}}
<li><select class="cap" ng-model="discounts.components" ng-options="i for (i,d) in discounts.opts" ng-change="updateDiscount()"></select></li>
</ul>
<hr />
<ul>
{{'builds' | translate}} & {{'comparisons' | translate}}
<li><a href="#" class="block" ng-click="backup($event)" translate="backup"></a></li>
<li><a href="#" class="block" ng-click="detailedExport($event)" translate="detailed export"></a></li>
<li><a href="#" class="block" ui-sref="modal.import" translate="import"></a></li>
<li><a href="#" class="block" ui-sref="modal.delete" translate="delete all"></a></li>
</ul>
<hr />
<table style="width: 300px;background-color:transparent">
<tr>
<td style="width: 20px"><u>A</u></td>
<td slider min="0.65" def="sizeRatio" max="1.2" on-change="textSizeChange(val)" ignore-resize="true"></td>
<td style="width: 20px"><span style="font-size: 30px">A</span></td>
</tr>
<tr>
<td></td><td style="text-align:center" class="primary-disabled cap" ng-click="resetTextSize()" translate="reset"></td><td></td>
</tr>
</table>
<hr />
<a href="#" ui-sref="modal.about" class="block" translate="about"></a>
</div>);
}
}

View File

@@ -1,90 +0,0 @@
angular.module('app').directive('componentSelect', ['$translate', function($translate) {
// Generting the HTML in this manner is MUCH faster than using an angular template.
function appendGroup(list, opts, cid, mass, checkWarning) {
var prevClass = null, prevRating = null;
for (var i = 0; i < opts.length; i++) {
var o = opts[i];
var id = o.id || (o.class + o.rating); // Standard components' ID is their class and rating
if (i > 0 && opts.length > 3 && o.class != prevClass && (o.rating != prevRating || o.mode) && o.grp != 'pa') {
list.push('<br/>');
}
list.push('<li class="', o.name ? 'lc' : 'c');
if (cid == id) {
list.push(' active');
}
if (checkWarning && checkWarning(opts[i])) {
list.push(' warning');
}
list.push(((o.maxmass && (mass + (o.mass ? o.mass : 0)) > o.maxmass) ? ' disabled"' : '" cpid="' + id + '"'), '>');
if (o.mode) {
list.push('<svg class="icon lg"><use xlink:href="#mount-', o.mode, '"></use></svg> ');
}
list.push('<span>', o.class, o.rating);
if (o.missile) {
list.push('/' + o.missile);
}
if (o.name) {
list.push(' ' + $translate.instant(o.name));
}
list.push('</span></li>');
prevClass = o.class;
prevRating = o.rating;
}
}
return {
restrict: 'A',
scope: {
opts: '=', // Component Options object
groups: '=', // Groups of Component Options
mass: '=', // Current ship mass
s: '=', // Current Slot
warning: '=' // Check warning function
},
link: function(scope, element) {
var list = [];
var cid = scope.s.id; // Slot's current component id
var component = scope.s.c; // Slot's Current Component (may be null/undefined)
var opts = scope.opts;
var groups = scope.groups;
var mass = (scope.mass ? scope.mass : 0) - (component && component.mass ? component.mass : 0); // Mass minus the currently selected component
if (groups) {
// At present time slots with grouped options (Hardpoints and Internal) can be empty
list.push('<div class="empty-c upp" cpid="empty">', $translate.instant('empty'), '</div>');
for (var g in groups) {
var grp = groups[g];
var grpCode = grp[Object.keys(grp)[0]].grp; // Nasty operation to get the grp property of the first/any single component
list.push('<div id="', grpCode, '" class="select-group cap">', $translate.instant(g), '</div><ul>');
appendGroup(list, grp, cid, mass, scope.warning);
list.push('</ul>');
}
} else {
list.push('<ul>');
appendGroup(list, opts, cid, mass, scope.warning);
list.push('</ul>');
}
element.html(list.join(''));
// If groups are present and a component is already selectd
if (groups && component && component.grp) {
var groupElement = angular.element(document.getElementById(component.grp));
var parentElem = element[0].parentElement;
parentElem.scrollTop = groupElement[0].offsetTop; // Scroll to currently selected group
}
}
};
}]);

View File

@@ -1,114 +0,0 @@
angular.module('app').directive('shipyardHeader', ['lodash', '$window', '$rootScope', '$state', 'Persist', 'Serializer', 'ShipsDB', function(_, $window, $rootScope, $state, Persist, Serializer, ships) {
return {
restrict: 'E',
templateUrl: 'views/_header.html',
scope: true,
link: function(scope) {
scope.openedMenu = null;
scope.ships = ships;
scope.allBuilds = Persist.builds;
scope.buildsList = Object.keys(scope.allBuilds).sort();
scope.allComparisons = Object.keys(Persist.comparisons).sort();
scope.bs = Persist.state;
var win = angular.element($window); // Angularized window object for event triggering
var insIndex = _.findIndex($rootScope.insurance.opts, 'name', Persist.getInsurance());
var savedDiscounts = Persist.getDiscount() || [1, 1];
$rootScope.insurance.current = $rootScope.insurance.opts[insIndex != -1 ? insIndex : 0];
$rootScope.discounts.ship = savedDiscounts[0];
$rootScope.discounts.components = savedDiscounts[1];
/**
* Save selected insurance option
*/
scope.updateInsurance = function() {
Persist.setInsurance($rootScope.insurance.current.name);
};
/**
* Save selected discount option
*/
scope.updateDiscount = function() {
Persist.setDiscount([$rootScope.discounts.ship, $rootScope.discounts.components]);
$rootScope.$broadcast('discountChange');
};
scope.backup = function(e) {
e.preventDefault();
e.stopPropagation();
scope.openedMenu = null;
$state.go('modal.export', {
title: 'backup',
data: Persist.getAll(),
description: 'PHRASE_BACKUP_DESC'
});
};
scope.detailedExport = function(e) {
e.preventDefault();
e.stopPropagation();
scope.openedMenu = null;
$state.go('modal.export', {
title: 'detailed export',
data: Serializer.toDetailedExport(scope.allBuilds),
description: 'PHRASE_EXPORT_DESC'
});
};
scope.cleanedBuildList = function(shipId) {
return Object.keys(scope.allBuilds[shipId]);
};
scope.openMenu = function(e, menu) {
e.stopPropagation();
if (menu == scope.openedMenu) {
scope.openedMenu = null;
return;
}
if ((menu == 'comp' || menu == 'b') && !scope.bs.hasBuilds) {
scope.openedMenu = null;
return;
}
if (menu == 'comp') {
scope.allComparisons = Object.keys(Persist.comparisons).sort();
}
scope.openedMenu = menu;
};
// Close menus if a navigation change event occurs
$rootScope.$on('$stateChangeStart', function() {
scope.openedMenu = null;
});
// Listen to close event to close opened menus or modals
$rootScope.$on('close', function() {
scope.openedMenu = null;
});
scope.textSizeChange = function(size) {
if (size != $rootScope.sizeRatio) {
$rootScope.sizeRatio = size;
document.getElementById('main').style.fontSize = size + 'em';
Persist.setSizeRatio(size);
win.triggerHandler('resize');
}
};
scope.resetTextSize = function() {
if ($rootScope.sizeRatio != 1) {
scope.textSizeChange(1);
scope.$broadcast('reset', 1);
}
};
scope.$watchCollection('allBuilds', function() {
scope.buildsList = Object.keys(scope.allBuilds).sort();
});
}
};
}]);

View File

@@ -1,9 +0,0 @@
angular.module('app').directive('loader', function() {
return {
restrict: 'A',
link: function(scope, element) {
element.addClass('loader');
element.html('<svg viewbox="0 0 40 40" width="100%" height="100%"><path d="m5,8l5,8l5,-8z" class="l1 d1" /><path d="m5,8l5,-8l5,8z" class="l1 d2" /><path d="m10,0l5,8l5,-8z" class="l1 d3" /><path d="m15,8l5,-8l5,8z" class="l1 d4" /><path d="m20,0l5,8l5,-8z" class="l1 d5" /><path d="m25,8l5,-8l5,8z" class="l1 d6" /><path d="m25,8l5,8l5,-8z" class="l1 d7" /><path d="m30,16l5,-8l5,8z" class="l1 d8" /><path d="m30,16l5,8l5,-8z" class="l1 d9" /><path d="m25,24l5,-8l5,8z" class="l1 d10" /><path d="m25,24l5,8l5,-8z" class="l1 d11" /><path d="m20,32l5,-8l5,8z" class="l1 d13" /><path d="m15,24l5,8l5,-8z" class="l1 d14" /><path d="m10,32l5,-8l5,8z" class="l1 d15" /><path d="m5,24l5,8l5,-8z" class="l1 d16" /><path d="m5,24l5,-8l5,8z" class="l1 d17" /><path d="m0,16l5,8l5,-8z" class="l1 d18" /><path d="m0,16l5,-8l5,8z" class="l1 d20" /><path d="m10,16l5,-8l5,8z" class="l2 d0" /><path d="m15,8l5,8l5,-8z" class="l2 d3" /><path d="m20,16l5,-8l5,8z" class="l2 d6" /><path d="m20,16l5,8l5,-8z" class="l2 d9" /><path d="m15,24l5,-8l5,8z" class="l2 d12" /><path d="m10,16l5,8l5,-8z" class="l2 d15" /></svg>');
}
};
});

View File

@@ -1,13 +0,0 @@
angular.module('app').directive('slotHardpoint', ['$rootScope', function($r) {
return {
restrict: 'A',
scope: {
hp: '=',
size: '='
},
templateUrl: 'views/_slot-hardpoint.html',
link: function(scope) {
scope.$r = $r;
}
};
}]);

View File

@@ -1,13 +0,0 @@
angular.module('app').directive('slotInternal', ['$rootScope', function($r) {
return {
restrict: 'A',
scope: {
c: '=slot',
fuel: '='
},
templateUrl: 'views/_slot-internal.html',
link: function(scope) {
scope.$r = $r;
}
};
}]);

View File

@@ -1,52 +0,0 @@
import EN from 'en';
import DE from 'de';
import ES from 'es';
import FR from 'fr';
import IT from 'it';
import RU from 'RU';
import d3 from 'd3';
let fallbackTerms = EN.terms;
let currentLanguage;
let currentTerms;
let format = {
rPct: d3.format('%')
};
export format;
export function setLanguage(langCode) {
let lang;
switch (langCode) {
case 'de': lang = DE; break;
case 'es': lang = ES; break;
case 'fr': lang = FR; break;
case 'it': lang = IT; break;
case 'ru': lang = RU; break;
default: lang = EN;
}
currentTerms = lang.terms;
d3Locale = d3.locale(lang.formats);
format.gen = d3Locale.numberFormat('n');
format.crd = d3Locale.numberFormat(',.0f');
format.pwr = d3Locale.numberFormat(',.2f');
format.round = (d) => format.gen(d3.round(d, 2));
format.pct = d3Locale.numberFormat('.2%');
format.pct1 = d3Locale.numberFormat('.1%');
}
export const Languages = {
en: 'English',
de: 'Deutsh',
it: 'Italiano',
es: 'Español',
fr: 'Français',
ru: 'ру́сский'
};
export function term(t) {
return currentTerms[t] || fallbackTerms[t];
}

View File

@@ -1,114 +0,0 @@
angular.module('app', ['ui.router', 'ct.ui.router.extras.sticky', 'ui.sortable', 'shipyard', 'ngLodash', 'app.templates', 'pascalprecht.translate'])
.run(['$rootScope', '$location', '$window', '$document', '$state', '$translate', 'localeFormat', 'Persist', 'Discounts', 'Languages', 'SizeMap',
function($rootScope, $location, $window, $doc, $state, $translate, localeFormat, Persist, Discounts, Languages, SizeMap) {
// App is running as a standalone web app on tablet/mobile
var isStandAlone;
// This was causing issues on Windows phones ($window.external was causing Angular js to throw an exception). Backup is to try this and set isStandAlone to false if this fails.
try {
isStandAlone = $window.navigator.standalone || ($window.external && $window.external.msIsSiteMode && $window.external.msIsSiteMode());
} catch (ex) {
isStandAlone = false;
}
// Redirect any state transition errors to the error controller/state
$rootScope.$on('$stateChangeError', function(e, toState, toParams, fromState, fromParams, error) {
e.preventDefault();
$state.go('error', error, { location: false, reload: true }); // Go to error state, reload the controller, keep the current URL
});
// Track on Google analytics if available
$rootScope.$on('$stateChangeSuccess', function(e, to, toParams, from, fromParams) {
$rootScope.prevState = { name: from.name, params: fromParams };
if (to.url) { // Only track states that have a URL
if ($window.ga) {
ga('send', 'pageview', { page: $location.path() });
}
if (isStandAlone) {
// Persist the current state
Persist.setState({ name: to.name, params: toParams });
}
}
});
$rootScope.language = {
opts: Languages,
current: Languages[Persist.getLangCode()] ? Persist.getLangCode() : 'en'
};
$rootScope.localeFormat = d3.locale(localeFormat.get($rootScope.language.current));
updateNumberFormat();
// Global Reference variables
$rootScope.insurance = { opts: [{ name: 'standard', pct: 0.05 }, { name: 'alpha', pct: 0.025 }, { name: 'beta', pct: 0.0375 }] };
$rootScope.discounts = { opts: Discounts };
$rootScope.sizeRatio = Persist.getSizeRatio();
$rootScope.SZM = SizeMap;
$rootScope.title = 'Coriolis';
$rootScope.changeLanguage = function() {
$translate.use($rootScope.language.current);
$rootScope.localeFormat = d3.locale(localeFormat.get($rootScope.language.current));
updateNumberFormat();
$rootScope.$broadcast('languageChanged', $rootScope.language.current);
};
// Formatters
$rootScope.fRPct = d3.format('%');
$rootScope.fTime = function(d) { return Math.floor(d / 60) + ':' + ('00' + Math.floor(d % 60)).substr(-2, 2); };
function updateNumberFormat() {
var locale = $rootScope.localeFormat;
var fGen = $rootScope.fGen = locale.numberFormat('n');
$rootScope.fCrd = locale.numberFormat(',.0f');
$rootScope.fPwr = locale.numberFormat(',.2f');
$rootScope.fRound = function(d) { return fGen(d3.round(d, 2)); };
$rootScope.fPct = locale.numberFormat('.2%');
$rootScope.f1Pct = locale.numberFormat('.1%');
}
/**
* Returns the name of the component mounted in the specified slot
* @param {Object} slot The slot object
* @return {String} The component name
*/
$rootScope.cName = function(slot) {
return $translate.instant(slot.c ? slot.c.name ? slot.c.name : slot.c.grp : null);
};
// Global Event Listeners
$doc.bind('keyup', function(e) {
if (e.keyCode == 27) { // Escape Key
$rootScope.$broadcast('close', e);
$rootScope.$apply();
} else {
$rootScope.$broadcast('keyup', e);
}
});
$rootScope.bgClicked = function(e) {
$rootScope.$broadcast('close', e);
};
if ($window.applicationCache) {
// Listen for appcache updated event, present refresh to update view
$window.applicationCache.addEventListener('updateready', function() {
if ($window.applicationCache.status == $window.applicationCache.UPDATEREADY) {
// Browser downloaded a new app cache.
$rootScope.appCacheUpdate = true;
$rootScope.$apply();
}
}, false);
}
if (isStandAlone) {
var state = Persist.getState();
// If a previous state has been stored, load that state
if (state && state.name && state.params) {
$state.go(state.name, state.params, { location: 'replace' });
} else {
$state.go('shipyard', null, { location: 'replace' }); // Default to home page
}
}
}]);

View File

@@ -1,12 +0,0 @@
import { Component } from 'react';
export default class ShipyardPage extends Component {
constructor(props) {
super(props);
}
render() {
return <div>Page {this.props.path} Not Found</div>;
}
}

View File

@@ -1,180 +0,0 @@
import { Component } from 'react';
import { Ships, Components } from 'coriolis-data';
import cn from 'classnames';
import Ship from 'Ship';
function countHp(slot) {
this.hp[slot.maxClass]++;
this.hpCount++;
}
function countInt(slot) {
var crEligible = !slot.eligible || slot.eligible.cr;
this.int[slot.maxClass - 1]++; // Subtract 1 since there is no Class 0 Internal compartment
this.intCount++;
this.maxCargo += crEligible ? Components.findInternal('cr', slot.maxClass, 'E').capacity : 0;
}
function shipSummary(shipId, shipData) {
let summary = {
id: shipId,
hpCount: 0,
intCount: 0,
maxCargo: 0,
hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge
int: [0, 0, 0, 0, 0, 0, 0, 0] // Sizes 1 - 8
};
Object.assign(summary, shipData.properties);
let ship = new Ship(shipId, shipData.properties, shipData.slots);
// Build Ship
ship.buildWith(shipData.defaults); // Populate with stock/default components
ship.hardpoints.forEach(countHp.bind(summary)); // Count Hardpoints by class
ship.internal.forEach(countInt.bind(summary)); // Count Internal Compartments by class
summary.retailCost = ship.totalCost; // Record Stock/Default/retail cost
ship.optimizeMass({ pd: '1D' }); // Optimize Mass with 1D PD for maximum possible jump range
summary.maxJumpRange = ship.unladenRange; // Record Jump Range
ship.optimizeMass({ th: ship.standard[1].maxClass + 'A' }); // Optmize mass with Max Thrusters
summary.topSpeed = ship.topSpeed;
summary.topBoost = ship.topBoost;
return summary;
}
let shipSummaries = [];
for (var s in Ships) {
shipSummaries.push(shipSummary(s, Ships[s]));
}
export default class ShipyardPage extends Component {
constructor(props) {
super(props);
this.state = {
title: 'Coriolis - Shipyard',
shipPredicate: 'properties.name',
shipDesc = false
};
}
shouldComponentUpdate(nextProps, nextState) {
// Only on language change. Context?
return false;
}
/**
* Sort ships
* @param {object} key Sort predicate
*/
_sortShips(shipPredicate, shipPredicateIndex) {
let shipDesc = this.state.shipPredicate == shipPredicate ? !this.state.shipDesc : this.state.shipDesc;
this.setState({ shipPredicate, shipDesc, shipPredicateIndex });
};
render() {
let sortShips = this._sortShips.bind(this);
let shipPredicate = this.state.shipPredicate;
let shipPredicateIndex = this.state.shipPredicateIndex;
let shipRows = [];
// Sort shipsOverview
shipSummaries.sort((a, b) => {
let valA = a[shipPredicate], valB = b[shipPredicate];
if (shipPredicateIndex != undefined) {
valA = valA[shipPredicateIndex];
valB = valB[shipPredicateIndex];
}
return this.state.shipDesc ? (valA > valB) : (valB > valA);
});
for (s of shipSummaries) {
shipRows.push(
<tr className={'highlight'}>
<td className={'le'}><a ui-sref='outfit({shipId: s.id})'>{s.name}</a></td>
<td className={'le'}>{s.manufacturer}</td>
<td className={'cap'}>{SZM[s.class] | translate}</td>
<td className={'ri'}>{{fCrd(s.speed)}} <u translate>m/s</u></td>
<td className={'ri'}>{{fCrd(s.boost)}} <u translate>m/s</u></td>
<td className={'ri'}>{s.baseArmour}</td>
<td className={'ri'}>{{fCrd(s.baseShieldStrength)}} <u translate>Mj</u></td>
<td className={'ri'}>{{fCrd(s.topSpeed)}} <u translate>m/s</u></td>
<td className={'ri'}>{{fCrd(s.topBoost)}} <u translate>m/s</u></td>
<td className={'ri'}>{{fRound(s.maxJumpRange)}} <u translate>LY</u></td>
<td className={'ri'}>{{fCrd(s.maxCargo)}} <u translate>T</u></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>
<td className={'ri'}>{fCrd(s.hullMass)} <u translate>T</u></td>
<td className={'ri'}>{s.masslock}</td>
<td className={'ri'}>{fCrd(s.retailCost)} <u translate>CR</u></td>
</tr>
);
}
return (
<div id='shipyard'>
<div className={'scroll-x'}>
<table style={{ fontSize:'0.85em', whiteSpace:'nowrap' }}>
<thead>
<tr className={'main'}>
<th rowspan='2' className={'sortable le'} onClick={sortShips('name')} translate='ship'></th>
<th rowspan='2' className={'sortable'} onClick={sortShips('manufacturer')} translate='manufacturer'></th>
<th rowspan='2' className={'sortable'} onClick={sortShips('class')} translate='size'></th>
<th colspan='4' translate='base'></th>
<th colspan='4' translate='max'></th>
<th colspan='5' className={'sortable'} onClick={sortShips('hpCount')} translate='hardpoints'></th>
<th colspan='8' className={'sortable'} onClick={sortShips('intCount')} translate='internal compartments'></th>
<th rowspan='2' className={'sortable'} onClick={sortShips('hullMass')} translate='hull'></th>
<th rowspan='2' className={'sortable'} onClick={sortShips('masslock')} translate='MLF'></th>
<th rowspan='2' className={'sortable'} onClick={sortShips('retailCost')} translate='cost'></th>
</tr>
<tr>
{/* Base */}
<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>
{/* Max */}
<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>
{/* Hardpoints */}
<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>
{/* Internal */}
<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>
{shipRows}
</tbody>
</table>
</div>
</div>
);
}
}

View File

@@ -1,7 +0,0 @@
angular.module('app').controller('DeleteController', ['$scope', 'Persist', function($scope, Persist) {
$scope.deleteAll = function() {
Persist.deleteAll();
$scope.$parent.dismiss();
};
}]);

View File

@@ -1,29 +0,0 @@
angular.module('app')
.controller('ErrorController', ['$window', '$rootScope', '$scope', '$stateParams', '$location', function($window, $rootScope, $scope, $p, $location) {
$rootScope.title = 'Error';
$scope.path = $location.path();
$scope.type = $p.type || 'unknown';
$scope.browser = $window.navigator.userAgent;
switch ($scope.type) {
case 404:
$scope.msgPre = 'Page';
$scope.msgHighlight = $scope.path;
$scope.msgPost = 'Not Found';
break;
case 'no-ship':
$scope.msgPre = 'Ship';
$scope.msgHighlight = $p.message;
$scope.msgPost = 'does not exist';
break;
case 'build-fail':
$scope.msgPre = 'Build Failure!';
$scope.details = $p.details;
break;
default:
$scope.msgPre = 'Uh, Jameson, we have a problem..';
$scope.errorMessage = $p.message;
$scope.details = $p.details;
}
}]);

View File

@@ -1,19 +0,0 @@
angular.module('app').controller('ExportController', ['$scope', '$stateParams', function($scope, $stateParams) {
$scope.title = $stateParams.title || 'Export';
$scope.description = $stateParams.description;
if ($stateParams.promise) {
$scope.export = 'Generating...';
$stateParams.promise.then(function(data) {
$scope.export = (typeof data === 'object') ? angular.toJson(data, true) : data;
});
} else {
$scope.export = angular.toJson($stateParams.data, true);
}
$scope.onTextClick = function($event) {
$event.target.select();
};
}]);

View File

@@ -1,316 +0,0 @@
angular.module('app').controller('ImportController', ['lodash', '$rootScope', '$scope', '$stateParams', 'ShipsDB', 'Ship', 'Components', 'GroupMap', 'Persist', 'Serializer', function(_, $rootScope, $scope, $stateParams, Ships, Ship, Components, GroupMap, Persist, Serializer) {
$scope.importValid = false;
$scope.importString = null;
$scope.errorMsg = null;
$scope.canEdit = true;
$scope.builds = $stateParams.obj || null;
$scope.ships = Ships;
var textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n');
var lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)');
var mountMap = { 'H': 4, 'L': 3, 'M': 2, 'S': 1, 'U': 0 };
var standardMap = { 'RB': 0, 'TM': 1, 'FH': 2, 'EC': 3, 'PC': 4, 'SS': 5, 'FS': 6 };
var bhMap = { 'lightweight alloy': 0, 'reinforced alloy': 1, 'military grade composite': 2, 'mirrored surface composite': 3, 'reactive surface composite': 4 };
function isEmptySlot(slot) {
return slot.maxClass == this && slot.c === null;
}
function equalsIgnoreCase(str) {
return str.toLowerCase() == this.toLowerCase();
}
function validateBuild(shipId, code, name) {
var shipData = Ships[shipId];
if (!shipData) {
throw '"' + shipId + '" is not a valid Ship Id!';
}
if (typeof name != 'string' || name.length == 0) {
throw shipData.properties.name + ' build "' + name + '" must be a string at least 1 character long!';
}
if (typeof code != 'string' || code.length < 10) {
throw shipData.properties.name + ' build "' + name + '" is not valid!';
}
try {
Serializer.toShip(new Ship(shipId, shipData.properties, shipData.slots), code);
} catch (e) {
throw shipData.properties.name + ' build "' + name + '" is not valid!';
}
}
function detailedJsonToBuild(detailedBuild) {
var ship;
if (!detailedBuild.name) {
throw 'Build Name missing!';
}
if (!detailedBuild.name.trim()) {
throw 'Build Name must be a string at least 1 character long!';
}
try {
ship = Serializer.fromDetailedBuild(detailedBuild);
} catch (e) {
throw detailedBuild.ship + ' Build "' + detailedBuild.name + '": Invalid data';
}
return { shipId: ship.id, name: detailedBuild.name, code: Serializer.fromShip(ship) };
}
function importBackup(importData) {
if (importData.builds && typeof importData.builds == 'object') {
for (var shipId in importData.builds) {
for (var buildName in importData.builds[shipId]) {
validateBuild(shipId, importData.builds[shipId][buildName], buildName);
}
}
$scope.builds = importData.builds;
} else {
throw 'builds must be an object!';
}
if (importData.comparisons) {
for (var compName in importData.comparisons) {
var comparison = importData.comparisons[compName];
for (var i = 0, l = comparison.builds.length; i < l; i++) {
var build = comparison.builds[i];
if (!importData.builds[build.shipId] || !importData.builds[build.shipId][build.buildName]) {
throw build.shipId + ' build "' + build.buildName + '" data is missing!';
}
}
}
$scope.comparisons = importData.comparisons;
}
if (importData.discounts instanceof Array && importData.discounts.length == 2) {
$scope.discounts = importData.discounts;
}
if (typeof importData.insurance == 'string' && importData.insurance.length > 3) {
$scope.insurance = importData.insurance;
}
}
function importDetailedArray(importArr) {
var builds = {};
for (var i = 0, l = importArr.length; i < l; i++) {
var build = detailedJsonToBuild(importArr[i]);
if (!builds[build.shipId]) {
builds[build.shipId] = {};
}
builds[build.shipId][build.name] = build.code;
}
$scope.builds = builds;
}
function importTextBuild(buildStr) {
var buildName = textBuildRegex.exec(buildStr)[1].trim();
var shipName = buildName.toLowerCase();
var shipId = null;
for (var sId in Ships) {
if (Ships[sId].properties.name.toLowerCase() == shipName) {
shipId = sId;
break;
}
}
if (!shipId) { throw 'No such ship found: "' + buildName + '"'; }
var lines = buildStr.split('\n');
var ship = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots);
ship.buildWith(null);
for (var i = 1; i < lines.length; i++) {
var line = lines[i].trim();
if (!line) { continue; }
if (line.substring(0, 3) == '---') { break; }
var parts = lineRegex.exec(line);
if (!parts) { throw 'Error parsing: "' + line + '"'; }
var typeSize = parts[1];
var cl = parts[2];
var rating = parts[3];
var mount = parts[4];
var missile = parts[5];
var name = parts[6].trim();
var slot, group;
if (isNaN(typeSize)) { // Standard or Hardpoint
if (typeSize.length == 1) { // Hardpoint
var slotClass = mountMap[typeSize];
if (cl > slotClass) { throw cl + rating + ' ' + name + ' exceeds slot size: "' + line + '"'; }
slot = _.find(ship.hardpoints, isEmptySlot, slotClass);
if (!slot) { throw 'No hardpoint slot available for: "' + line + '"'; }
group = _.find(GroupMap, equalsIgnoreCase, name);
var hp = Components.findHardpoint(group, cl, rating, group ? null : name, mount, missile);
if (!hp) { throw 'Unknown component: "' + line + '"'; }
ship.use(slot, hp.id, hp, true);
} else if (typeSize == 'BH') {
var bhId = bhMap[name.toLowerCase()];
if (bhId === undefined) { throw 'Unknown bulkhead: "' + line + '"'; }
ship.useBulkhead(bhId, true);
} else if (standardMap[typeSize] != undefined) {
var standardIndex = standardMap[typeSize];
if (ship.standard[standardIndex].maxClass < cl) { throw name + ' exceeds max class for the ' + ship.name; }
ship.use(ship.standard[standardIndex], cl + rating, Components.standard(standardIndex, cl + rating), true);
} else {
throw 'Unknown component: "' + line + '"';
}
} else {
if (cl > typeSize) { throw cl + rating + ' ' + name + ' exceeds slot size: "' + line + '"'; }
slot = _.find(ship.internal, isEmptySlot, typeSize);
if (!slot) { throw 'No internal slot available for: "' + line + '"'; }
group = _.find(GroupMap, equalsIgnoreCase, name);
var intComp = Components.findInternal(group, cl, rating, group ? null : name);
if (!intComp) { throw 'Unknown component: "' + line + '"'; }
ship.use(slot, intComp.id, intComp);
}
}
var builds = {};
builds[shipId] = {};
builds[shipId]['Imported ' + buildName] = Serializer.fromShip(ship);
$scope.builds = builds;
}
$scope.validateImport = function() {
var importData = null;
var importString = $scope.importString.trim();
$scope.importValid = false;
$scope.errorMsg = null;
$scope.builds = $scope.discounts = $scope.comparisons = $scope.insurance = null;
if (!importString) { return; }
try {
if (textBuildRegex.test(importString)) { // E:D Shipyard build text
importTextBuild(importString);
} else { // JSON Build data
importData = angular.fromJson($scope.importString);
if (!importData || typeof importData != 'object') {
throw 'Must be an object or array!';
}
if (importData instanceof Array) { // Must be detailed export json
importDetailedArray(importData);
} else if (importData.ship && typeof importData.name !== undefined) { // Using JSON from a single ship build export
importDetailedArray([importData]); // Convert to array with singleobject
} else { // Using Backup JSON
importBackup(importData);
}
}
} catch (e) {
$scope.errorMsg = (typeof e == 'string') ? e : 'Cannot Parse the data!';
return;
}
$scope.importValid = true;
};
$scope.hasBuild = function(shipId, name) {
return Persist.getBuild(shipId, name) !== null;
};
$scope.hasComparison = function(name) {
return Persist.getComparison(name) !== null;
};
$scope.process = function() {
if ($scope.builds) {
var builds = $scope.builds;
for (var shipId in builds) {
for (var buildName in builds[shipId]) {
var code = builds[shipId][buildName];
// Update builds object such that orginal name retained, but can be renamed
builds[shipId][buildName] = {
code: code,
useName: buildName
};
}
}
}
if ($scope.comparisons) {
var comparisons = $scope.comparisons;
for (var name in comparisons) {
comparisons[name].useName = name;
}
}
$scope.processed = true;
};
$scope.import = function() {
if ($scope.builds) {
var builds = $scope.builds;
for (var shipId in builds) {
for (var buildName in builds[shipId]) {
var build = builds[shipId][buildName];
var name = build.useName.trim();
if (name) {
Persist.saveBuild(shipId, name, build.code);
}
}
}
}
if ($scope.comparisons) {
var comparisons = $scope.comparisons;
for (var comp in comparisons) {
var comparison = comparisons[comp];
var useName = comparison.useName.trim();
if (useName) {
Persist.saveComparison(useName, comparison.builds, comparison.facets);
}
}
}
if ($scope.discounts) {
$rootScope.discounts.ship = $scope.discounts[0];
$rootScope.discounts.components = $scope.discounts[1];
$rootScope.$broadcast('discountChange');
Persist.setDiscount($scope.discounts);
}
if ($scope.insurance) {
$rootScope.insurance.current = $scope.insurance;
Persist.setInsurance($scope.insurance);
}
$scope.$parent.dismiss();
};
/* Initialization */
if ($scope.builds) { // If import is passed an build object
$scope.canEdit = false;
$scope.process();
}
}]);

View File

@@ -1,16 +0,0 @@
angular.module('app').controller('LinkController', ['$scope', 'Utils', '$stateParams', function($scope, Utils, $stateParams) {
$scope.url = $stateParams.url;
$scope.shortenedUrl = 'Shortening...';
$scope.onTextClick = function($event) {
$event.target.select();
};
Utils.shortenUrl($scope.url)
.then(function(url) {
$scope.shortenedUrl = url;
}, function(e) {
$scope.shortenedUrl = 'Error - ' + e.statusText;
});
}]);

View File

@@ -1,14 +0,0 @@
angular.module('app').controller('ModalController', ['$rootScope', '$scope', '$state', function($rootScope, $scope, $state) {
$scope.dismiss = function() {
if ($rootScope.prevState) {
var state = $rootScope.prevState;
$state.go(state.name, state.params, { location: 'replace', reload: false });
} else {
$state.go('shipyard');
}
};
$scope.$on('close', $scope.dismiss);
}]);

View File

@@ -1,10 +0,0 @@
export default function shortenUrl(url) {
/*if (window.navigator.onLine) {
return $http.post(shortenAPI + GAPI_KEY, { longUrl: url }).then(function(response) {
return response.data.id;
});
} else {
return $q.reject({ statusText: 'Not Online' });
}*/
}

View File

@@ -1,8 +0,0 @@
.deep-space {
background-image: url(images/deep-space-1920x1080.jpg);
}
.docking-bay {
background-image: url(images/bay-1920x1080.jpg);
}

View File

@@ -1,26 +0,0 @@
@font-face {
font-family: 'Orbitron-Regular';
src: url('fonts/orbitron-regular-webfont.eot');
src: url('fonts/orbitron-regular-webfont.eot?#iefix') format('embedded-opentype'),
url('fonts/orbitron-regular-webfont.woff2') format('woff2'),
url('fonts/orbitron-regular-webfont.woff') format('woff'),
url('fonts/orbitron-regular-webfont.ttf') format('truetype'),
url('fonts/orbitron-regular-webfont.svg#orbitronregular') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Eurostile';
src: url('fonts/eurostile.eot');
src: url('fonts/eurostile.eot?#iefix') format('embedded-opentype'),
url('fonts/eurostile.woff2') format('woff2'),
url('fonts/eurostile.woff') format('woff'),
url('fonts/eurostile.ttf') format('truetype'),
url('fonts/eurostile.svg#euro_capsregular') format('svg');
font-weight: normal;
font-style: normal;
}
@fStandard: 'Eurostile', Helvetica, sans-serif;
@fTitle: 'Orbitron-Regular', Arial, sans-serif;

View File

@@ -1,3 +0,0 @@
<div class="modal-bg">
<div class="modal" ui-view="modal-content" ng-click="$event.stopPropagation()"></div>
</div>

View File

@@ -1,17 +0,0 @@
<div class="sz">{{['U','S','M','L','H'][hp.maxClass] | translate}}</div>
<div class="empty" ng-if="!hp.c" translate="empty"></div>
<div ng-if="hp.c">
{{hp.c.class}}{{hp.c.rating}}<span ng-if="hp.c.mode">/{{hp.c.mode}}{{hp.c.missile}}</span> {{hp.c.name || hp.c.grp | translate}}
<div class="r">{{hp.c.mass}} <u>T</u></div>
<div class="cb">
<div class="l" ng-if="hp.c.damage">{{'damage' | translate}}: {{hp.c.damage}} <span ng-if="hp.c.ssdam">({{$r.fCrd(hp.c.ssdam)}} <u>MJ</u>)</span></div>
<div class="l" ng-if="hp.c.dps">{{'DPS' | translate}}: {{hp.c.dps}} <span ng-if="hp.c.mjdps">({{$r.fCrd(hp.c.mjdps)}} <u>MJ</u>)</span></div>
<div class="l" ng-if="hp.c.thermload">{{'T_LOAD' | translate}}: {{hp.c.thermload}}</div>
<div class="l" ng-if="hp.c.type">{{'type' | translate}}: {{hp.c.type}}</div>
<div class="l" ng-if="hp.c.rof">{{'ROF' | translate}}: {{hp.c.rof}}<u>/s</u></div>
<div class="l" ng-if="hp.c.armourpen">{{'pen' | translate}}: {{hp.c.armourpen}}</div>
<div class="l" ng-if="hp.c.shieldmul">+{{$r.fRPct(hp.c.shieldmul)}}</div>
<div class="l" ng-if="hp.c.range">{{hp.c.range}} <u>km</u></div>
<div class="l" ng-if="hp.c.ammo >= 0">{{'ammo' | translate}}: {{$r.fCrd(hp.c.clip)}}+{{$r.fCrd(hp.c.ammo)}}</div>
</div>
</div>

View File

@@ -1,22 +0,0 @@
<div class="sz" ng-bind="c.maxClass"></div>
<div class="empty" ng-if="!c.c" translate="empty"></div>
<div ng-if="c.c">
<div class="l name">{{c.c.class}}{{c.c.rating}} {{c.c.name || c.c.grp | translate}}</div>
<div class="r">{{c.c.mass || c.c.capacity || '0'}} <u translate="T"></u></div>
<div class="cb"></div>
<div class="l" ng-if="c.c.optmass">{{'optimal mass' | translate}}: {{c.c.optmass}} <u translate="T"></u></div>
<div class="l" ng-if="c.c.maxmass">{{'max mass' | translate}}: {{c.c.maxmass}} <u translate="T"></u></div>
<div class="l" ng-if="c.c.bins">{{c.c.bins}} <u translate="bins"></u></div>
<div class="l" ng-if="c.c.rate">{{'rate' | translate}}: {{c.c.rate}} <u>kg/s</u>&nbsp;&nbsp;&nbsp;{{'refuel time' | translate}}: {{$r.fTime(fuel * 1000 / c.c.rate)}}</div>
<div class="l" ng-if="c.c.ammo">{{'ammo' | translate}}: {{$r.fCrd(c.c.ammo)}}</div>
<div class="l" ng-if="c.c.cells">{{'cells' | translate}}: {{c.c.cells}}</div>
<div class="l" ng-if="c.c.recharge">{{'recharge' | translate}}: {{c.c.recharge}} <u>MJ</u>&nbsp;&nbsp;&nbsp;{{'total' | translate}}: {{c.c.cells * c.c.recharge}} <u>MJ</u></div>
<div class="l" ng-if="c.c.repair">{{'repair' | translate}}: {{c.c.repair}}</div>
<div class="l" ng-if="c.c.range">{{'range' | translate}} {{c.c.range}} <u>km</u></div>
<div class="l" ng-if="c.c.time">{{'time' | translate}}: {{$r.fTime(c.c.time)}}</div>
<div class="l" ng-if="c.c.maximum">{{'max' | translate}}: {{(c.c.maximum)}}</div>
<div class="l" ng-if="c.c.rangeLS">{{c.c.rangeLS}} <u translate="LS"></u></div>
<div class="l" ng-if="c.c.rangeLS === null"><svg class="icon"><use xlink:href="#infinite"></use></svg> <u translate="LS"></u></div>
<div class="l" ng-if="c.c.rangeRating">{{'range' | translate}}: {{c.c.rangeRating}}</div>
<div class="l" ng-if="c.c.armouradd">+{{c.c.armouradd}} <u translate="armour"></u></div>
</div>

View File

@@ -1,29 +0,0 @@
<h1><svg class="icon xl"><use xlink:href="#coriolis"></use></svg> <span class="warning">Coriolis</span></h1>
<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.</p>
<div>
<a class="l" style="margin: 0.4em" href="https://github.com/cmmcleod/coriolis" target="_blank" title="Coriolis Github Project">
<svg class="icon fg xl"><use xlink:href="#github-mark"></use></svg>
</a>
<h2>Github</h2>
<a href="https://github.com/cmmcleod/coriolis" target="_blank" title="Coriolis Github Project">github.com/cmmcleod/coriolis</a>
</div>
<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>
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
<input type="hidden" name="cmd" value="_s-xclick">
<input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----MIIHLwYJKoZIhvcNAQcEoIIHIDCCBxwCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYCjl3XoqQ3Q+x/qS7Va1lwvF0IgUs8gBbrwj1/uEv/xFyPSB2G0kgWqiB2c/8vvfcjjyMr4nlzLUlmQ0yl1zZaeTXFciN5a+JsvaBISThIlN9UP7PXP61TVHCECtt/hBNtlOmg8/gG8khJCj8+qi81XsNAz5bEDpdahKW3fwGHD4jELMAkGBSsOAwIaBQAwgawGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQI4EVkn3RE+9qAgYhg2sTmY1Gul2yJyLYJZPRMO/PwgzEogb2NlIcshJSO+KvBea5NjjTXN2EJNqJa24h4lGA1mdrSgzTGDrVbdcnuti9+7ggn5R5s5IwEEQnN4JQx3IAqsp3UmJbti5t776Ns50nQbjA8NzxI+gwUmIvUQaVs6wC4HYXG6q8QtqUIWeVDhvbnt+H8oIIDhzCCA4MwggLsoAMCAQICAQAwDQYJKoZIhvcNAQEFBQAwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tMB4XDTA0MDIxMzEwMTMxNVoXDTM1MDIxMzEwMTMxNVowgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBR07d/ETMS1ycjtkpkvjXZe9k+6CieLuLsPumsJ7QC1odNz3sJiCbs2wC0nLE0uLGaEtXynIgRqIddYCHx88pb5HTXv4SZeuv0Rqq4+axW9PLAAATU8w04qqjaSXgbGLP3NmohqM6bV9kZZwZLR/klDaQGo1u9uDb9lr4Yn+rBQIDAQABo4HuMIHrMB0GA1UdDgQWBBSWn3y7xm8XvVk/UtcKG+wQ1mSUazCBuwYDVR0jBIGzMIGwgBSWn3y7xm8XvVk/UtcKG+wQ1mSUa6GBlKSBkTCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb22CAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCBXzpWmoBa5e9fo6ujionW1hUhPkOBakTr3YCDjbYfvJEiv/2P+IobhOGJr85+XHhN0v4gUkEDI8r2/rNk1m0GA8HKddvTjyGw/XqXa+LSTlDYkqI8OwR8GEYj4efEtcRpRYBxV8KxAW93YDWzFGvruKnnLbDAF6VR5w/cCMn5hzGCAZowggGWAgEBMIGUMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbQIBADAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTUwNjA1MjEwNDQzWjAjBgkqhkiG9w0BCQQxFgQUx6Bs50H7tbYJln13pP5J7J1KiSUwDQYJKoZIhvcNAQEBBQAEgYBiOr1RX38uvwghuIZxKpjXX4LG/GoyYM6citfsBD5vjUGj0udmsamjlur+dwxJNs9dULnJO6huoTvxqxTui0Mh3n21YKoMqVE/erfNk2XygrJw9bEtW+HXjU3F+OGKR7dfD9STp2ZlvTEvZR9JRV5A/udC9/9U9eD5iLKRRwkIBg==-----END PKCS7-----
">
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif" name="submit" alt="PayPal - Donate to Coriolis.io" style="border:none;">
<img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1">
</form>
<p>Help keep the lights on! Donations will be used to cover costs of running and maintaining Coriolis. Thanks for helping!</p>
<button class="r dismiss cap" ng-click="dismiss()" translate="close"></button>

View File

@@ -1,4 +0,0 @@
<h2 translate="delete all"></h2>
<p style="text-align:center;" translate="PHRASE_CONFIRMATION"></p>
<button class="l cap" ng-click="deleteAll()" translate="yes"></button>
<button class="r cap" ng-click="dismiss()" translate="no"></button>

View File

@@ -1,6 +0,0 @@
<h2 ng-bind="title | translate"></h2>
<div ng-if="description" ng-bind="description | translate"></div>
<div>
<textarea class="cb json" ng-click="onTextClick($event)" ng-bind="export"></textarea>
</div>
<button class="r dismiss cap" ng-click="dismiss()" translate="close"></button>

View File

@@ -1,46 +0,0 @@
<h2 translate="import"></h2>
<div ng-show="!processed">
<textarea class="cb json" ng-model="importString" ng-change="validateImport()" placeholder="{{'PHRASE_IMPORT' | translate}}"></textarea>
<button class="l cap" ng-click="process()" ng-disabled="!importValid" translate="proceed"></button>
<div class="l warning" style="margin-left:3em;">{{errorMsg}}</div>
</div>
<div ng-show="processed">
<table class="l" style="overflow:hidden;margin: 1em 0; width: 100%;">
<thead>
<tr>
<th style="text-align:left" translate="ship"></th>
<th style="text-align:left" translate="build name"></th>
<th translate="action"></th>
</tr>
</thead>
<tbody ng-repeat="(shipId,shipBuilds) in builds">
<tr class="cb" ng-repeat="(buildName, b) in shipBuilds">
<td>{{ships[shipId].properties.name}}</td>
<td><input type="text" ng-model="b.useName"/></td>
<td style="text-align:center" ng-class="{warning: hasBuild(shipId, b.useName) == true, disabled: b.useName == ''}">
<span ng-show="b.useName" translate="{{ hasBuild(shipId, b.useName)? 'overwrite' : 'create' }}"></span>
<span ng-show="b.useName == ''" translate="skip"></span>
</td>
</tr>
</tbody>
</table>
<table class="l" style="overflow:hidden;margin: 1em 0; width: 100%;" ng-if="comparisons">
<thead><tr><th style="text-align:left" translate="comparison"></th><th translate="action"></th></tr></thead>
<tbody>
<tr class="cb" ng-repeat="(name, comparison) in comparisons">
<td><input type="text" ng-model="comparison.useName"/></td>
<td style="text-align:center" ng-class="{warning: hasComparison(comparison.useName) == true, disabled: comparison.useName == ''}">
<span ng-show="comparison.useName" translate="{{ hasComparison(comparison.useName)? 'overwrite' : 'create' }}"></span>
<span ng-show="comparison.useName == ''" translate="skip"></span>
</td>
</tr>
</tbody>
</table>
<button class="cl l" ng-click="import()"><svg class="icon"><use xlink:href="#download"></use></svg> {{'import' | translate}}</button>
<button class="l cap" style="margin-left: 2em;" ng-click="processed = false" ng-show="canEdit" translate="edit data"></button>
</div>
<button class="r dismiss cap" ng-click="dismiss()" translate="cancel"></button>

View File

@@ -1,9 +0,0 @@
<h2 translate="permalink"></h2>
<br>
<h3 translate="URL"></h3>
<input ng-model="url" size="40" ng-click="onTextClick($event)">
<br><br>
<h3 translate="shortened"></h3>
<input ng-model="shortenedUrl" size="25" ng-click="onTextClick($event)">
<br><br>
<button class="r dismiss cap" ng-click="dismiss()" translate="close"></button>

View File

@@ -1,74 +0,0 @@
<table id="comparison">
<tr ng-show="compareMode">
<td class="head" translate="comparison"></td>
<td>
<input ng-model="name" ng-change="nameChange()" placeholder="{{'enter name' | translate}}" maxlength="50" />
<button ng-click="save()" ng-disabled="!name || name == 'all' || saved">
<svg class="icon lg "><use xlink:href="#floppy-disk"></use></svg><span class="button-lbl"> {{'save' | translate}}</span>
</button>
<button ng-click="delete()" ng-disabled="name == 'all' || !saved"><svg class="icon lg warning "><use xlink:href="#bin"></use></svg></button>
<button ng-click="selectBuilds(true, $event)">
<svg class="icon lg "><use xlink:href="#rocket"></use></svg><span class="button-lbl"> {{'builds' | translate}}</span>
</button>
<button class="r" ng-click="permalink($event)" ng-disabled="builds.length == 0">
<svg class="icon lg "><use xlink:href="#link"></use></svg><span class="button-lbl"> {{'permalink' | translate}}</span>
</button>
<button class="r" ng-click="embed($event)" ng-disabled="builds.length == 0">
<svg class="icon lg "><use xlink:href="#embed"></use></svg><span class="button-lbl"> {{'forum' | translate}}</span>
</button>
</td>
</tr>
<tr ng-show="!compareMode">
<td class="head" translate="comparison"></td>
<td>
<h3 ng-bind="name"></h3>
<button class="r" ui-sref="modal.import({obj:importObj})"><svg class="icon lg "><use xlink:href="#download"></use></svg> {{'import' | translate}}</button>
</td>
</tr>
<tr>
<td class="head" translate="compare"></td>
<td>
<ul id="facet-container" as-sortable="facetSortOpts" ng-model="facets" class="sortable" update="tblUpdate">
<li ng-repeat="(i,f) in facets" as-sortable-item class="facet" ng-class="{active: f.active}" ng-click="toggleFacet(i)">
<div as-sortable-item-handle>&#x2194; <span ng-bind="f.title | translate"></span></div>
</li>
</ul>
</td>
</tr>
</table>
<div class="scroll-x">
<table id="comp-tbl" comparison-table ng-click="handleClick($event)"></table>
</div>
<div ng-repeat="f in facets | filter:{active:true}" ng-if="builds.length > 0" class="chart" bar-chart facet="f" data="builds">
<h3 ng-click="sort(f.props[0])" >{{f.title | translate}}</h3>
</div>
<div class="modal-bg" ng-show="showBuilds" ng-click="selectBuilds(false, $event)">
<div class="modal" ui-view="modal-content" ng-click="$event.stopPropagation()">
<h3 translate="PHRASE_SELECT_BUILDS"></h3>
<div id="build-select">
<table>
<thead><tr><th colspan="2" translate="available"></th></tr></thead>
<tbody>
<tr ng-repeat="b in unusedBuilds | orderBy:'name'" ng-click="addBuild(b.id, b.buildName)">
<td class="tl" ng-bind="b.name"></td><td class="tl" ng-bind="b.buildName"></td>
</tr>
</tbody>
</table>
<h1></h1>
<table>
<thead><tr><th colspan="2" translate="added"></th></tr></thead>
<tbody>
<tr ng-repeat="b in builds | orderBy:'name'" ng-click="removeBuild(b.id, b.buildName)">
<td class="tl" ng-bind="b.name"></td><td class="tl" ng-bind="b.buildName"></td>
</tr>
</tbody>
</table>
</div>
<br>
<button class="r dismiss cap" ng-click="selectBuilds(false, $event)" translate="done"></button>
</div>
</div>

View File

@@ -1,21 +0,0 @@
<div class="error">
<h1>
<span ng-if="msgPre">{{msgPre}}</span>
<small ng-if="msgHighlight">{{msgHighlight}}</small>
<span ng-if="msgPost">{{msgPost}}</span>
</h1>
<div style="text-align:left; font-size:0.8em; width: 43em; margin:0 auto;" >
<div class="cen">
<a href="https://github.com/cmmcleod/ed-shipyard" target="_blank" title="Coriolis Github Project">Create an issue on Github</a>
if this keeps happening. <a href="#" ng-click="showDetails = !showDetails">Add these details</a>
</div>
<div style="margin-top: 2em;" ng-show="showDetails">
<div>Browser:<br>{{browser}}</div>
<div ng-if="path" >Path:<br>{{path}}</div>
<div ng-if="type">Error:<br>{{type}}</div>
<div ng-if="errorMessage">Message:<pre>{{errorMessage}}</pre></div>
<div ng-if="details">Details:<br><pre>{{details}}</pre></div>
</div>
</div>
</div>

27
devServer.js Normal file
View File

@@ -0,0 +1,27 @@
var path = require('path');
var express = require('express');
var webpack = require('webpack');
var config = require('./webpack.config.dev');
var app = express();
var compiler = webpack(config);
app.use(require('webpack-dev-middleware')(compiler, {
noInfo: true,
publicPath: config.output.publicPath
}));
app.use(require('webpack-hot-middleware')(compiler));
app.get('*', function(req, res) {
res.sendFile(path.join(__dirname, 'src/index.html'));
});
app.listen(3300, 'localhost', function(err) {
if (err) {
console.log(err);
return;
}
console.log('Listening at http://localhost:3000');
});

View File

@@ -1,281 +0,0 @@
// Build / Built-in dependencies
var gulp = require('gulp'),
exec = require('child_process').exec,
pkg = require('./package.json');
// Package.json / Gulp Dependencies
var appCache = require("gulp-manifest"),
concat = require('gulp-concat'),
del = require('del'),
eslint = require('gulp-eslint');
gutil = require('gulp-util'),
htmlmin = require('gulp-htmlmin'),
jsonlint = require("gulp-jsonlint"),
karma = require('karma').server,
less = require('gulp-less'),
mainBowerFiles = require('main-bower-files'),
minifyCSS = require('gulp-minify-css'),
revAll = require('gulp-rev-all'),
runSequence = require('run-sequence'),
sourcemaps = require('gulp-sourcemaps'),
svgstore = require('gulp-svgstore'),
svgmin = require('gulp-svgmin'),
template = require('gulp-template'),
templateCache = require('gulp-angular-templatecache'),
uglify = require('gulp-uglify');
var cdnHostStr = '';
gulp.task('less', function() {
return gulp.src('app/less/app.less')
.pipe(less({paths: ['less/app.less']}).on('error',function(e){
console.log('File:', e.fileName);
console.log('Line:', e.lineNumber);
console.log('Message:', e.message);
this.emit('end');
}))
.pipe(minifyCSS())
.pipe(gulp.dest('build'));
});
gulp.task('js-lint', function() {
return gulp.src(['app/js/**/*.js', '!app/js/template_cache.js', '!app/js/db.js'])
.pipe(eslint({
globals: { angular:1, DB:1, d3:1, ga:1, GAPI_KEY:1, LZString: 1 },
rules: {
quotes: [2, 'single'],
strict: 'global',
eqeqeq: 'smart',
'space-after-keywords': [2, 'always'],
'no-use-before-define': 'no-func',
'space-before-function-paren': [2, 'never'],
'space-before-blocks': [2, 'always'],
'object-curly-spacing': [2, "always"],
'brace-style': [2, '1tbs', { allowSingleLine: true }],
'no-control-regex': false
},
envs: ['browser']
}))
.pipe(eslint.format())
.pipe(eslint.failAfterError());
});
gulp.task('json-lint', function() {
return gulp.src(['data/**/*.json' , 'app/schemas/**/*.json'])
.pipe(jsonlint())
.pipe(jsonlint.reporter())
.pipe(jsonlint.failAfterError());
});
gulp.task('bower', function(){
return gulp.src(mainBowerFiles())
.pipe(uglify({mangle: false, compress: false}).on('error',function(e){
console.log('Bower File:', e.fileName);
console.log('Line:', e.lineNumber);
console.log('Message:', e.message);
}))
.pipe(concat('lib.js'))
.pipe(gulp.dest('build'));
});
gulp.task('html2js', function() {
return gulp.src('app/views/**/*.html')
.pipe(htmlmin({
'collapseBooleanAttributes': true,
'collapseWhitespace': true,
'removeAttributeQuotes': true,
'removeComments': true,
'removeEmptyAttributes': true,
'removeRedundantAttributes': true,
'removeScriptTypeAttributes': true,
'removeStyleLinkTypeAttributes': true
}).on('error',function(e){
console.log('File:', e.fileName);
console.log('Message:',e.message);
}))
.pipe(templateCache({
'module': 'app.templates',
'standalone': true,
'root': 'views',
'filename': 'template_cache.js'
}))
.pipe(gulp.dest('app/js'))
});
gulp.task('jsonToDB', function(cb) {
exec('node scripts/json-to-db.js', cb);
});
gulp.task('js', function() {
return gulp.src([
'app/js/db.js',
'app/js/**/module-*.js',
'app/js/template_cache.js',
'app/js/app.js',
'app/js/**/*.js'
])
.pipe(sourcemaps.init())
.pipe(uglify({mangle: false}).on('error',function(e){
console.log('File:', e.fileName);
console.log('Line:', e.lineNumber);
console.log('Message:', e.message);
this.emit('end');
}))
.pipe(concat('app.js'))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('build'));
});
gulp.task('copy', function() {
return gulp.src(['app/images/**','app/fonts/**','app/db.json', 'app/schemas/**'], {base: 'app/'})
.pipe(gulp.dest('build'));
});
gulp.task('generateIndexHTML', function(done) {
// Generate minified inline svg of all icons for svg spriting
gulp.src('app/icons/*.svg')
.pipe(svgmin())
.pipe(svgstore({ inlineSvg: true }))
.pipe(gutil.buffer(function(err, files) {
var svgIconsContent = files[0].contents.toString();
gulp.src('app/index.html')
.pipe(template({
version: pkg.version,
date : new Date().toISOString().slice(0, 10),
uaTracking: process.env.CORIOLIS_UA_TRACKING || false,
svgContent: svgIconsContent,
gapiKey: process.env.CORIOLIS_GAPI_KEY
}))
.pipe(htmlmin({
'collapseBooleanAttributes': true,
'collapseWhitespace': true,
'removeAttributeQuotes': true,
'removeComments': true,
'removeEmptyAttributes': true,
'removeRedundantAttributes': true,
'removeScriptTypeAttributes': true,
'removeStyleLinkTypeAttributes': true
}).on('error',function(e){
console.log('File:', e.fileName);
console.log('Message:',e.message);
}))
.pipe(gulp.dest('build'));
done();
}));
});
gulp.task('serve', function(cb) {
exec('nginx -p $(pwd) -c nginx.conf', function (err, stdout, stderr) {
if (stderr) {
console.warn(stderr);
console.warn('Is NGINX already running?\n');
}
cb();
});
});
// Windows command to launch nginx serv
gulp.task('serve-win', function(cb) {
exec('nginx -p %cd% -c nginx.conf', function (err, stdout, stderr) {
if (stderr) {
console.warn(stderr);
console.warn('Is NGINX already running?\n');
}
cb();
});
});
gulp.task('serve-stop', function(cb) {
exec('kill -QUIT $(cat nginx.pid)', function (err, stdout, stderr) {
if (stderr) console.log(stderr); else cb(err);
});
});
gulp.task('watch', function() {
gulp.watch(['app/index.html','app/icons/*.svg'], ['generateIndexHTML']);
gulp.watch(['app/images/**','app/fonts/**', 'app/db.json', 'app/schemas/**'], ['copy']);
gulp.watch('app/less/*.less', ['less']);
gulp.watch('app/views/**/*', ['html2js']);
gulp.watch('app/js/**/*.js', ['js']);
gulp.watch('data/**/*.json', ['jsonToDB']);
gulp.watch(['build/**', '!**/*.appcache'], ['appcache']);
});
gulp.task('cache-bust', function(done) {
var rev_all = new revAll({ prefix: cdnHostStr, dontRenameFile: ['.html','.json'] });
var stream = gulp.src('build/**')
.pipe(rev_all.revision())
.pipe(gulp.dest('build'))
.pipe(rev_all.manifestFile())
.pipe(gulp.dest('build'));
stream.on('end', function() {
var manifest = require('./build/rev-manifest.json');
var arr = [];
for(var origFileName in manifest) {
if(origFileName != manifest[origFileName]) { // For all files busted/renamed
arr.push('./build/' + origFileName); // Add the original filename to the list
}
}
del(arr, done); // Delete all originals files the were not busted/renamed
});
stream.on('error', done);
});
gulp.task('appcache', function(done) {
// Since using a CDN manually build file list rather than using appCache mechanisms
gulp.src(['build/**', '!build/index.html', '!**/*.json', '!**/logo/*', '!**/*.map','!**/*.appcache'])
.pipe(gutil.buffer(function(err, stream) {
var files = [];
for (var i = 0; i < stream.length; i++) {
if (!stream[i].isNull()) {
files.push(cdnHostStr + '/' + stream[i].relative);
}
}
gulp.src([])
.pipe(appCache({
preferOnline: true,
cache: files,
filename: 'coriolis.appcache',
timestamp: true
}))
.pipe(gulp.dest('build'))
.on('end', done);
}));
});
gulp.task('upload', function(done) {
exec([
"rsync -e 'ssh -i ", process.env.CORIOLIS_PEM, "' -a --delete build/ ", process.env.CORIOLIS_USER, "@", process.env.CORIOLIS_HOST, ":~/www"
].join(''),
done
);
});
gulp.task('test', function (done) {
karma.start({
configFile: __dirname + '/test/karma.conf.js',
singleRun: true
}, function(exitStatus) {
done(exitStatus ? new gutil.PluginError('karma', { message: 'Unit tests failed!' }) : undefined);
});
});
gulp.task('lint', ['js-lint', 'json-lint']);
gulp.task('clean', function (done) { del(['build'], done); });
gulp.task('build', function (done) { runSequence('clean', ['html2js','jsonToDB'], ['generateIndexHTML','bower','js','less','copy'], done); });
gulp.task('build-cache', function (done) { runSequence('build', 'appcache', done); });
gulp.task('build-prod', function (done) { runSequence('build', 'cache-bust', 'appcache', done); });
gulp.task('dev', function (done) { runSequence('build-cache', 'serve','watch', done); });
gulp.task('deploy', function (done) {
cdnHostStr = '//cdn.' + process.env.CORIOLIS_HOST;
runSequence('build-prod', 'upload', done);
});
gulp.task('default', ['dev']);

View File

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

View File

@@ -10,41 +10,45 @@
"private": true, "private": true,
"engine": "node >= 0.12.2", "engine": "node >= 0.12.2",
"license": "MIT", "license": "MIT",
"scripts": {
"clean": "rimraf build",
"build:prod": "NODE_ENV=production webpack --config webpack.config.prod.js",
"build": "npm run clean && npm run build:prod",
"start": "node devServer.js",
"lint": "eslint src"
},
"devDependencies": { "devDependencies": {
"async": "0.9.x", "babel-core": "^5.4.7",
"del": "1.2.x", "babel-eslint": "^3.1.9",
"gulp": "3.9.x", "babel-loader": "^5.1.2",
"gulp-concat": "2.5.x", "babel-plugin-react-transform": "^1.1.1",
"gulp-eslint": "0.13.x", "css-loader": "^0.23.0",
"gulp-jasmine": "2.0.x", "eslint": "^1.3.1",
"gulp-jsonlint": "1.1.x", "eslint-plugin-react": "^2.3.0",
"gulp-less": "3.0.x", "express": "^4.13.3",
"gulp-manifest": "0.0.6", "file-loader": "^0.8.4",
"gulp-minify-css": "1.1.x", "install": "^0.3.0",
"gulp-rev-all": "0.8.18", "json-loader": "^0.5.3",
"gulp-sourcemaps": "1.5.x", "less": "^2.5.3",
"gulp-svgmin": "1.1.x", "less-loader": "^2.2.1",
"gulp-svgstore": "5.0.x", "npm": "^3.4.0",
"gulp-template": "3.0.x", "react-transform-catch-errors": "^1.0.0",
"gulp-uglify": "1.2.x", "react-transform-hmr": "^1.0.0",
"gulp-util": "3.0.x", "redbox-react": "^1.0.1",
"jasmine-core": "2.3.x", "rimraf": "^2.4.3",
"jsen": "^0.6.0", "style-loader": "^0.13.0",
"json-concat": "0.0.x", "url-loader": "^0.5.6",
"karma": "0.12.x", "webpack": "^1.9.6",
"karma-fixture": "^0.2.5", "webpack-dev-middleware": "^1.2.0",
"karma-jasmine": "0.3.x", "webpack-hot-middleware": "^2.0.0"
"karma-json-fixtures-preprocessor": "0.0.4",
"karma-mocha-reporter": "1.0.x",
"karma-phantomjs-launcher": "0.2.x",
"phantomjs": "1.9.x",
"run-sequence": "1.1.x",
"uglify-js": "2.4.x"
}, },
"dependencies": { "dependencies": {
"classnames": "^2.2.0", "classnames": "^2.2.0",
"d3": "^3.5.9",
"fbemitter": "^2.0.0", "fbemitter": "^2.0.0",
"lz-string": "^1.4.4",
"react": "^0.14.2", "react": "^0.14.2",
"react-dom": "^0.14.2" "react-dom": "^0.14.2",
"superagent": "^1.4.0"
} }
} }

View File

@@ -1,90 +0,0 @@
var fs = require('fs');
var UglifyJS = require('uglify-js');
var jsonConcat = require('json-concat');
var async = require('async');
var db_filename = './app/js/db.js';
async.parallel([
function(cb) { jsonConcat({ dest: null, src: './data/ships' }, done.bind(cb)); },
function(cb) {
var standard = [
JSON.parse(fs.readFileSync('./data/components/standard/power_plant.json', 'utf8')),
JSON.parse(fs.readFileSync('./data/components/standard/thrusters.json', 'utf8')),
JSON.parse(fs.readFileSync('./data/components/standard/frame_shift_drive.json', 'utf8')),
JSON.parse(fs.readFileSync('./data/components/standard/life_support.json', 'utf8')),
JSON.parse(fs.readFileSync('./data/components/standard/power_distributor.json', 'utf8')),
JSON.parse(fs.readFileSync('./data/components/standard/sensors.json', 'utf8')),
JSON.parse(fs.readFileSync('./data/components/standard/fuel_tank.json', 'utf8'))
];
cb(null, standard);
},
function(cb) { jsonConcat({ dest: null, src: './data/components/hardpoints' }, done.bind(cb)); },
function(cb) { jsonConcat({ dest: null, src: './data/components/internal' }, done.bind(cb)); },
function(cb) { jsonConcat({ dest: null, src: ['./data/components/bulkheads.json'] }, done.bind(cb)); }
], writeDB);
function done(err, json) { this(err,json); }
function writeDB(err, arr) {
var ships = {}, internal = {}, hardpoints = {};
var shipOrder = Object.keys(arr[0]).sort(function(a,b) { return arr[0][a].properties.name < arr[0][b].properties.name ? -1 : 1; });
var internalOrder = Object.keys(arr[3]).sort();
var hpOrder = [
"pl",
"ul",
"bl",
"mc",
"c",
"fc",
"rg",
"pa",
"mr",
"tp",
"nl",
"ml",
"cs",
"cm",
"ws",
"kw",
"sb"
];
for (var i = 0; i < internalOrder.length; i++) {
internal[internalOrder[i]] = arr[3][internalOrder[i]];
}
for (var j = 0; j < hpOrder.length; j++) {
hardpoints[hpOrder[j]] = arr[2][hpOrder[j]];
}
for (var s = 0; s < shipOrder.length; s++) {
ships[shipOrder[s]] = arr[0][shipOrder[s]];
}
try {
var db = {
ships: ships,
components: {
standard: arr[1],
hardpoints: hardpoints,
internal: internal,
bulkheads: arr[4]
}
};
}
catch (e) {
console.error(arguments);
exit(0);
}
var ast = UglifyJS.parse('var DB = ' + JSON.stringify(db));
var code = ast.print_to_string({beautify: true, indent_level: 2});
fs.open(db_filename, 'w', function() {
fs.writeFile(db_filename, code, function(err) {});
});
fs.open('./app/db.json', 'w', function() {
fs.writeFile('./app/db.json', JSON.stringify(db), function(err) {});
});
}

110
src/app/Coriolis.jsx Normal file
View File

@@ -0,0 +1,110 @@
import React from 'react';
import Router from './Router';
import { getLanguage } from './i18n/Language';
import Persist from './stores/Persist';
import InterfaceEvents from './utils/InterfaceEvents';
import Header from './components/Header';
import AboutPage from './pages/AboutPage';
import NotFoundPage from './pages/NotFoundPage';
import OutfittingPage from './pages/OutfittingPage';
import ShipyardPage from './pages/ShipyardPage';
export default class Coriolis extends React.Component {
static childContextTypes = {
language: React.PropTypes.object.isRequired,
route: React.PropTypes.object
};
constructor() {
super();
this._setPage = this._setPage.bind(this);
this.state = {
page: null,
language: getLanguage(Persist.getLangCode()),
route: null
};
Router('', (r) => this._setPage(ShipyardPage, r));
// Router('/', (ctx) => this._setPage(ShipyardPage, ctx));
Router('/outfitting/:ship', (r) => this._setPage(OutfittingPage, r));
Router('/outfitting/:ship/:code', (r) => this._setPage(OutfittingPage, r));
// Router('/compare/:name', compare);
// Router('/comparison/:code', comparison);
// Router('/settings', settings);
Router('/about', (r) => this._setPage(AboutPage, r));
Router('*', (r) => this._setPage(null, r));
}
_setPage(page, route) {
this.setState({ page, route });
}
_onError(msg, scriptUrl, line, col, errObj) {
this._setPage(<div>Some errors occured!!</div>);
}
_onLanguageChange(lang) {
this.setState({ language: getLanguage(Persist.getLangCode()) });
}
_keyDown(e) {
switch (e.keyCode) {
case 27:
InterfaceEvents.closeAll();
this._hideModal();
break;
}
}
_showModal(content) {
let modal = <div className='modal-bg' onClick={(e) => this._hideModal() }>{content}</div>;
this.setState({ modal });
}
_hideModal() {
if (this.state.modal) {
this.setState({ modal: null });
}
}
getChildContext() {
return {
language: this.state.language,
route: this.state.route
};
}
componentWillMount() {
// Listen for appcache updated event, present refresh to update view
if (window.applicationCache) {
window.applicationCache.addEventListener('updateready', () => {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
this.setState({appCacheUpdate: true}); // Browser downloaded a new app cache.
}
}, false);
}
window.onerror = this._onError.bind(this);
document.addEventListener('keydown', this._keyDown.bind(this));
Persist.addListener('language', this._onLanguageChange.bind(this));
Persist.addListener('language', this._onLanguageChange.bind(this));
InterfaceEvents.addListener('showModal', this._showModal.bind(this));
InterfaceEvents.addListener('hideModal', this._hideModal.bind(this));
Router.start();
}
render() {
return (
<div onClick={InterfaceEvents.closeAll}>
<Header appCacheUpdate={this.state.appCacheUpdate} />
{this.state.page? <this.state.page /> : <NotFoundPage/>}
{this.state.modal}
</div>
);
}
}

View File

@@ -1,4 +1,5 @@
import Persist from 'stores/Persist'; import Persist from './stores/Persist';
import InterfaceEvents from './utils/InterfaceEvents';
function isStandAlone() { function isStandAlone() {
try { try {
@@ -35,19 +36,6 @@ function Router(path, fn) {
Router.callbacks = []; Router.callbacks = [];
/**
* Bind with the given `options`.
*
* Options:
*
* - `click` bind to click events [true]
* - `popstate` bind to popstate [true]
* - `dispatch` perform initial dispatch [true]
*
* @param {Object} options
* @api public
*/
Router.start = function(){ Router.start = function(){
window.addEventListener('popstate', onpopstate, false); window.addEventListener('popstate', onpopstate, false);
@@ -62,7 +50,7 @@ Router.start = function(){
} }
} else { } else {
var url = location.pathname + location.search + location.hash; var url = location.pathname + location.search + location.hash;
Router.replace(url, null, true, dispatch); Router.replace(url, null, true, true);
} }
}; };
@@ -76,8 +64,9 @@ Router.start = function(){
*/ */
Router.go = function(path, state) { Router.go = function(path, state) {
gaTrack(path); gaTrack(path);
InterfaceEvents.closeAll();
var ctx = new Context(path, state); var ctx = new Context(path, state);
if (false !== dispatch) Router.dispatch(ctx); Router.dispatch(ctx);
if (!ctx.unhandled) ctx.pushState(); if (!ctx.unhandled) ctx.pushState();
return ctx; return ctx;
}; };
@@ -95,8 +84,7 @@ Router.replace = function(path, state, init, dispatch) {
gaTrack(path); gaTrack(path);
var ctx = new Context(path, state); var ctx = new Context(path, state);
ctx.init = init; ctx.init = init;
if (null == dispatch) dispatch = true; if (dispatch !== false) Router.dispatch(ctx);
if (dispatch) Router.dispatch(ctx);
ctx.save(); ctx.save();
return ctx; return ctx;
}; };

View File

@@ -0,0 +1,20 @@
import React from 'react';
import Link from './Link';
import cn from 'classnames';
export default class ActiveLink extends Link {
isActive = () => {
return encodeURI(this.props.href) == (window.location.pathname + window.location.search);
}
render() {
let className = this.props.className;
if (this.isActive()) {
className = cn(className, 'active');
}
return <a {...this.props} className={className} onClick={this.handler}>{this.props.children}</a>
}
}

View File

@@ -0,0 +1,103 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames';
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
export default class AvailableModulesMenu extends TranslatedComponent {
static propTypes = {
modules: React.PropTypes.oneOfType([ React.PropTypes.object, React.PropTypes.array ]).isRequired,
onSelect: React.PropTypes.func.isRequired,
m: React.PropTypes.object,
shipMass: React.PropTypes.number,
warning: React.PropTypes.func
};
static defaultProps = {
shipMass: 0
};
buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules) {
let prevClass = null, prevRating = null;
let elems = [];
for (let i = 0; i < modules.length; i++) {
let m = modules[i];
let classRating = m.class + m.rating;
let mount = null;
let classes = cn(m.name ? 'lc' : 'c', {
active: mountedModule && mountedModule.id === m.id,
warning: warningFunc && warningFunc(m),
disabled: m.maxmass && (mass + (m.mass ? m.mass : 0)) > m.maxmass
});
switch(m.mode) {
case 'F': mount = <MountFixed className={'lg'} />; break;
case 'G': mount = <MountGimballed className={'lg'}/>; break;
case 'T': mount = <MountTurret className={'lg'}/>; break;
}
if (i > 0 && modules.length > 3 && m.class != prevClass && (m.rating != prevRating || m.mode) && m.grp != 'pa') {
elems.push(<br key={m.grp + i} />);
}
elems.push(
<li key={m.id} className={classes} onClick={onSelect.bind(null, m)}>
{mount}
<span>{(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')}</span>
</li>
);
prevClass = m.class;
prevRating = m.rating;
}
return <ul key={'modules' + grp} >{elems}</ul>;
}
componentDidMount() {
let m = this.props.m
if (!(this.props.modules instanceof Array) && m && m.grp) {
findDOMNode(this).scrollTop = this.refs[m.grp].offsetTop; // Scroll to currently selected group
}
}
render() {
let translate = this.context.language.translate;
let m = this.props.m;
let modules = this.props.modules;
let list;
let buildGroup = this.buildGroup.bind(
null,
translate,
m,
this.props.warning,
this.props.shipMass - (m && m.mass ? m.mass : 0),
this.props.onSelect
);
if (modules instanceof Array) {
console.log(modules[0].grp, modules);
list = buildGroup(modules[0].grp, modules);
} else {
console.log('menu object')
list = [];
// At present time slots with grouped options (Hardpoints and Internal) can be empty
list.push(<div className={'empty-c upp'} key={'empty'} onClick={this.props.onSelect.bind(null, null)} >{translate('empty')}</div>);
for (let g in modules) {
let grp = modules[g];
let grpCode = grp[Object.keys(grp)[0]].grp; // Nasty operation to get the grp property of the first/any single component
list.push(<div ref={g} key={g} className={'select-group cap'}>{translate(g)}</div>);
list.push(buildGroup(g, modules[g]));
}
}
return (
<div className={cn('select', this.props.className)} onClick={(e) => e.stopPropagation() }>
{list}
</div>
);
}
}

View File

@@ -0,0 +1,38 @@
import React from 'react';
import Slot from './Slot';
export default class HardpointSlot extends Slot {
getClassNames() {
return this.props.size > 0 ? 'hardpoint' : null;
}
getSize(translate){
return translate(['U','S','M','L','H'][this.props.size]);
}
getSlotDetails(m, translate, formats, u) {
if (m) {
let classRating = `${m.class}${m.rating}${m.mode ? '/' + m.mode : ''}${m.missile ? m.missile : ''}`;
return (
<div>
<div className={'l'}>{classRating + ' ' + translate(m.name || m.grp)}</div>
<div className={'r'}>{m.mass}{u.T}</div>
<div className={'cb'}>
{ m.damage ? <div className={'l'}>{translate('damage')}: {m.damage} { m.ssdam ? <span>({formats.int(m.ssdam)} {u.MJ})</span> : null }</div> : null }
{ m.dps ? <div className={'l'}>{translate('DPS')}: {m.dps} { m.mjdps ? <span>({formats.int(m.mjdps)} {u.MJ})</span> : null }</div> : null }
{ m.thermload ? <div className={'l'}>{translate('T_LOAD')}: {m.thermload}</div> : null }
{ m.type ? <div className={'l'}>{translate('type')}: {m.type}</div> : null }
{ m.rof ? <div className={'l'}>{translate('ROF')}: {m.rof}{u.ps}</div> : null }
{ m.armourpen ? <div className={'l'}>{translate('pen')}: {m.armourpen}</div> : null }
{ m.shieldmul ? <div className={'l'}>+{formats.rPct(m.shieldmul)}</div> : null }
{ m.range ? <div className={'l'}>{m.range} <u>km</u></div> : null }
{ m.ammo >= 0 ? <div className={'l'}>{translate('ammo')}: {formats.int(m.clip)}+{formats.int(m.ammo)}</div> : null }
</div>
</div>
);
} else {
return <div className={'empty'}>{translate('empty')}</div>;
}
}
}

View File

@@ -0,0 +1,87 @@
import React from 'react';
import SlotSection from './SlotSection';
import HardpointSlot from './HardpointSlot';
import cn from 'classnames';
import { MountFixed, MountGimballed, MountTurret } from '../components/SvgIcons';
export default class HardpointsSlotSection extends SlotSection {
constructor(props, context) {
super(props, context, 'hardpoints', 'hardpoints');
this._empty = this._empty.bind(this);
}
_empty() {
}
_fill(grp, mount) {
}
_getSlots() {
let slots = [];
let hardpoints = this.props.ship.hardpoints;
let availableModules = this.props.ship.getAvailableModules();
let currentMenu = this.state.currentMenu;
for (let i = 0, l = hardpoints.length; i < l; i++) {
let h = hardpoints[i];
if (h.maxClass) {
slots.push(<HardpointSlot
key={i}
size={h.maxClass}
modules={availableModules.getHps(h.maxClass)}
onOpen={this._openMenu.bind(this,h)}
onSelect={this._selectModule.bind(this, h)}
selected={currentMenu == h}
m={h.m}
/>);
}
}
return slots;
}
_getSectionMenu(translate) {
let _fill = this._fill;
return <div className='select hardpoint' onClick={(e) => e.stopPropagation()}>
<ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
</ul>
<div className='select-group cap'>{translate('pl')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'pl', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'pl', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'pl', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('ul')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'ul', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'ul', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'ul', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('bl')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'bl', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'bl', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'bl', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('mc')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'mc', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'mc', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'mc', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('c')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'c', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'c', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'c', 'T')}><MountTurret className='lg'/></li>
</ul>
</div>;
}
}

View File

@@ -0,0 +1,251 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import Link from './Link';
import ActiveLink from './ActiveLink';
import cn from 'classnames';
import { Cogs, CoriolisLogo, Hammer, Rocket, StatsBars } from './SvgIcons';
import Ships from '../shipyard/Ships';
import InterfaceEvents from '../utils/InterfaceEvents';
import Persist from '../stores/Persist';
import ModalDeleteAll from './ModalDeleteAll';
export default class Header extends TranslatedComponent {
constructor(props) {
super(props);
this.state = {
openedMenu: null
};
this._close = this._close.bind(this);
this.shipOrder = Object.keys(Ships).sort();
}
_close() {
this.setState({ openedMenu: null });
}
_setInsurance(e) {
e.stopPropagation();
Persist.setInsurance('Beta'); // TODO: get insurance name
}
_setModuleDiscount(e) {
e.stopPropagation();
Persist.setModuleDiscount(0); // TODO: get module discount
}
_setShipDiscount(e) {
e.stopPropagation();
Persist.setShipDiscount(0); // TODO: get ship discount
}
_showDeleteAll(e) {
e.preventDefault();
InterfaceEvents.showModal(<ModalDeleteAll />);
this._close();
};
_showBackup(e) {
e.preventDefault();
/*$state.go('modal.export', {
title: 'backup',
data: Persist.getAll(),
description: 'PHRASE_BACKUP_DESC'
});*/
// TODO: implement modal
};
_showDetailedExport(e){
e.preventDefault();
e.stopPropagation();
/*$state.go('modal.export', {
title: 'detailed export',
data: Serializer.toDetailedExport(Persist.getBuilds()),
description: 'PHRASE_EXPORT_DESC'
});*/
// TODO: implement modal
}
_setTextSize(size) {
Persist.setSizeRatio(size); // TODO: implement properly
}
_resetTextSize() {
Persist.setSizeRatio(1);
}
_openMenu(event, openedMenu) {
event.stopPropagation();
if (this.state.openedMenu == openedMenu) {
openedMenu = null;
}
this.setState({ openedMenu });
}
_getShipsMenu() {
let shipList = [];
for (let s in Ships) {
shipList.push(<ActiveLink key={s} href={'/outfitting/' + s} className={'block'}>{Ships[s].properties.name}</ActiveLink>);
}
return (
<div className={'menu-list dbl no-wrap'} onClick={ (e) => e.stopPropagation() }>
{shipList}
</div>
);
}
_getBuildsMenu() {
let builds = Persist.getBuilds();
let buildList = [];
for (let shipId of this.shipOrder) {
if (builds[shipId]) {
let shipBuilds = [];
let buildNameOrder = Object.keys(builds[shipId]).sort();
for (let buildName of buildNameOrder) {
let href = ['/outfitting/', shipId, '/', builds[shipId][buildName], '?bn=', buildName].join('');
shipBuilds.push(<li key={shipId + '-' + buildName} ><ActiveLink href={href} className={'block'}>{buildName}</ActiveLink></li>);
}
buildList.push(<ul key={shipId}>{Ships[shipId].properties.name}{shipBuilds}</ul>);
}
}
return (
<div className={'menu-list'} onClick={ (e) => e.stopPropagation() }>
<div className={'dbl'}>{buildList}</div>
</div>
);
}
_getComparisonsMenu() {
let comparisons;
let translate = this.context.language.translate;
if (Persist.hasComparisons()) {
let comparisons = [];
let comps = Object.keys(Persist.getComparisons()).sort();
console.log(comps);
for (let name of comps) {
comparisons.push(<ActiveLink key={name} href={'/compare/' + name} className={'block name'}>{name}</ActiveLink>);
}
} else {
comparisons = <span className={'cap'}>{translate('none created')}</span>;
}
return (
<div className={'menu-list'} onClick={ (e) => e.stopPropagation() } style={{ whiteSpace: 'nowrap' }}>
{comparisons}
<hr />
<Link href='/compare/all' ui-sref="compare({name: 'all'})" className={'block cap'}>{translate('compare all')}</Link>
<Link href='/compare' className={'block cap'}>{translate('create new')}</Link>
</div>
);
}
_getSettingsMenu() {
let translate = this.context.language.translate;
return (
<div className={'menu-list no-wrap cap'} onClick={ (e) => e.stopPropagation() }>
<ul>
{translate('language')}
<li><select className={'cap'} ng-model="language.current" ng-options="langCode as langName for (langCode,langName) in language.opts" ng-change="changeLanguage()"></select></li>
</ul><br/>
<ul>
{translate('insurance')}
<li><select className={'cap'} ng-model="insurance.current" ng-options="ins.name | translate for (i,ins) in insurance.opts" ng-change="updateInsurance()"></select></li>
</ul><br/>
<ul>
{translate('ship')} {translate('discount')}
<li><select className={'cap'} ng-model="discounts.ship" ng-options="i for (i,d) in discounts.opts" ng-change="updateDiscount()"></select></li>
</ul><br/>
<ul>
{translate('component')} {translate('discount')}
<li><select className={'cap'} ng-model="discounts.components" ng-options="i for (i,d) in discounts.opts" ng-change="updateDiscount()"></select></li>
</ul>
<hr />
<ul>
{translate('builds')} & {translate('comparisons')}
<li><a href="#" className={'block'} ng-click="backup($event)">{translate('backup')}</a></li>
<li><a href="#" className={'block'} ng-click="detailedExport($event)">{translate('detailed export')}</a></li>
<li><a href="#" className={'block'} ui-sref="modal.import">{translate('import')}</a></li>
<li><a href="#" onClick={this._showDeleteAll.bind(this)}>{translate('delete all')}</a></li>
</ul>
<hr />
<table style={{width: 300, backgroundColor: 'transparent'}}>
<tbody>
<tr>
<td style={{width: 20}}><u>A</u></td>
<td slider min="0.65" def="sizeRatio" max="1.2" on-change="textSizeChange(val)" ignore-resize="true"></td>
<td style={{width: 20}}><span style={{fontSize: 30}}>A</span></td>
</tr>
<tr>
<td></td><td style={{ textAlign: 'center' }} className={'primary-disabled cap'} ng-click="resetTextSize()" translate="reset"></td><td></td>
</tr>
</tbody>
</table>
<hr />
<Link href="/about" className={'block'}>{translate('about')}</Link>
</div>
);
}
componentWillMount() {
this.closeAllListener = InterfaceEvents.addListener('closeAll', this._close);
}
componentWillUnmount() {
this.closeAllListener.remove();
}
render() {
let translate = this.context.language.translate;
let openedMenu = this.state.openedMenu;
let hasBuilds = Persist.hasBuilds();
if (this.props.appCacheUpdate) {
return <div id="app-update" onClick={window.location.reload}>{ 'PHRASE_UPDATE_RDY' | translate }</div>;
}
return (
<header>
<Link className={'l'} href="/" style={{marginRight: '1em'}} title="Home"><CoriolisLogo className={'icon xl'} /></Link>
<div className={'l menu'}>
<div className={cn('menu-header', {selected: openedMenu == 's'})} onClick={ (e) => this._openMenu(e,'s') } >
<Rocket className={'warning'} /><span className={'menu-item-label'}>{' ' + translate('ships')}</span>
</div>
{openedMenu == 's' ? this._getShipsMenu() : null}
</div>
<div className={'l menu'}>
<div className={cn('menu-header', {selected: openedMenu == 'b', disabled: !hasBuilds})} onClick={ hasBuilds ? (e) => this._openMenu(e,'b') : null }>
<Hammer className={cn('warning', { 'warning-disabled': !hasBuilds})} /><span className={'menu-item-label'}>{' ' + translate('builds')}</span>
</div>
{openedMenu == 'b' ? this._getBuildsMenu() : null}
</div>
<div className={'l menu'}>
<div className={cn('menu-header', {selected: openedMenu == 'comp', disabled: !hasBuilds})} onClick={ hasBuilds ? (e) => this._openMenu(e,'comp') : null }>
<StatsBars className={cn('warning', { 'warning-disabled': !hasBuilds})} /><span className={'menu-item-label'}>{' ' + translate('compare')}</span>
</div>
{openedMenu == 'comp' ? this._getComparisonsMenu() : null}
</div>
<div className={'r menu'}>
<div className={cn('menu-header', {selected: openedMenu == 'settings'})}onClick={ (e) => this._openMenu(e,'settings') }>
<Cogs className={'xl warning'}/><span className={'menu-item-label'}>{translate('settings')}</span>
</div>
{openedMenu == 'settings' ? this._getSettingsMenu() : null}
</div>
</header>
);
}
}

View File

@@ -0,0 +1,38 @@
import React from 'react';
import Slot from './Slot';
import { Infinite } from './SvgIcons';
export default class InternalSlot extends Slot {
getSlotDetails(m, translate, formats, u) {
if (m) {
let classRating = m.class + m.rating;
return (
<div>
<div className={'l'}>{classRating + ' ' + translate(m.name || m.grp)}</div>
<div className={'r'}>{m.mass || m.capacity || 0}{u.T}</div>
<div className={'cb'}>
{ m.optmass ? <div className={'l'}>{translate('optimal mass') + ': '}{m.optmass}{u.T}</div> : null }
{ m.maxmass ? <div className={'l'}>{translate('max mass') + ': '}{m.maxmass}{u.T}</div> : null }
{ m.bins ? <div className={'l'}>{m.bins + ' '}<u>{translate('bins')}</u></div> : null }
{ m.rate ? <div className={'l'}>{translate('rate')}: {m.rate}{u.kgs}&nbsp;&nbsp;&nbsp;{translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}</div> : null }
{ m.ammo ? <div className={'l'}>{translate('ammo')}: {formats.gen(m.ammo)}</div> : null }
{ m.cells ? <div className={'l'}>{translate('cells')}: {m.cells}</div> : null }
{ m.recharge ? <div className={'l'}>{translate('recharge')}: {m.recharge} <u>MJ</u>&nbsp;&nbsp;&nbsp;{translate('total')}: {m.cells * m.recharge}{u.MJ}</div> : null }
{ m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null }
{ m.range ? <div className={'l'}>{translate('range')} {m.range}{u.km}</div> : null }
{ m.time ? <div className={'l'}>{translate('time')}: {formats.time(m.time)}</div> : null }
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
{ m.rangeLS ? <div className={'l'}>{m.rangeLS}{u.Ls}</div> : null }
{ m.rangeLS === null ? <div className={'l'}><Infinite/>{u.Ls}</div> : null }
{ m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</div> : null }
{ m.armouradd ? <div className={'l'}>+{m.armouradd} <u>{translate('armour')}</u></div> : null }
</div>
</div>
);
} else {
return <div className={'empty'}>{translate('empty')}</div>;
}
}
}

View File

@@ -0,0 +1,69 @@
import React from 'react';
import SlotSection from './SlotSection';
import InternalSlot from './InternalSlot';
import cn from 'classnames';
export default class InternalSlotSection extends SlotSection {
constructor(props, context) {
super(props, context, 'internal', 'internal compartments');
this._empty = this._empty.bind(this);
this._fillWithCargo = this._fillWithCargo.bind(this);
this._fillWithCells = this._fillWithCells.bind(this);
this._fillWithArmor = this._fillWithArmor.bind(this);
}
_empty() {
}
_fillWithCargo() {
}
_fillWithCells() {
}
_fillWithArmor() {
}
_getSlots() {
let slots = [];
let {internal, fuelCapacity, ladenMass } = this.props.ship;
let availableModules = this.props.ship.getAvailableModules();
let currentMenu = this.state.currentMenu;
for (let i = 0, l = internal.length; i < l; i++) {
let s = internal[i];
slots.push(<InternalSlot
key={i}
size={s.maxClass}
modules={availableModules.getInts(s.maxClass, s.eligible)}
onOpen={this._openMenu.bind(this,s)}
onSelect={this._selectModule.bind(this, i, s)}
selected={currentMenu == s}
m={s.m}
fuel={fuelCapacity}
shipMass={ladenMass}
/>);
}
return slots;
}
_getSectionMenu(translate) {
return <div className='select' onClick={e => e.stopPropagation()}>
<ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
<li className='lc' onClick={this._fillWithCargo}>{translate('cargo')}</li>
<li className='lc' onClick={this._fillWithCells}>{translate('scb')}</li>
<li className='lc' onClick={this._fillWithArmor}>{translate('hr')}</li>
</ul>
</div>;
}
}

View File

@@ -0,0 +1,31 @@
import React from 'react';
import Router from '../Router';
import shallowEqual from '../utils/shallowEqual';
export default class Link extends React.Component {
shouldComponentUpdate(nextProps) {
return !shallowEqual(this.props, nextProps);
}
handler = (event) => {
if (event.getModifierState
&& ( event.getModifierState('Shift')
|| event.getModifierState('Alt')
|| event.getModifierState('Control')
|| event.getModifierState('Meta')
|| event.button > 1)) {
return;
}
event.nativeEvent && event.preventDefault && event.preventDefault();
if (this.props.href) {
Router.go(encodeURI(this.props.href));
}
}
render() {
return <a {...this.props} onClick={this.handler}>{this.props.children}</a>
}
}

View File

@@ -0,0 +1,22 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import InterfaceEvents from '../utils/InterfaceEvents';
export default class ModalDeleteAll extends TranslatedComponent {
_deleteAll() {
Persist.deleteAll();
InterfaceEvents.hideModal();
}
render() {
let translate = this.context.language.translate;
return <div className='modal' onClick={(e) => e.stopPropagation()}>
<h2>{translate('delete all')}</h2>
<p style={{textAlign: 'center'}}>{translate('PHRASE_CONFIRMATION')}</p>
<button className='l cap' onClick={this._deleteAll}>{translate('yes')}</button>
<button className='r cap' onClick={InterfaceEvents.hideModal}>{translate('no')}</button>
</div>;
}
}

View File

@@ -0,0 +1,49 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import InterfaceEvents from '../utils/InterfaceEvents';
export default class DeleteAllModal extends TranslatedComponent {
static propTypes = {
title: React.propTypes.string,
promise: : React.propTypes.func,
data: React.propTypes.oneOfType[React.propTypes.string, React.propTypes.object]
};
constructor(props) {
super(props);
let exportJson;
if (props.promise) {
exportJson = 'Generating...';
} else if(typeof props.data == 'string') {
exportJson = props.data;
} else {
exportJson = JSON.stringify(this.props.data);
}
this.state = { exportJson };
}
componentWillMount(){
// When promise is done update exportJson accordingly
}
render() {
let translate = this.context.language.translate;
let description;
if (this.props.description) {
description = <div>{translate(this.props.description)}</div>
}
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2>{translate(this.props.title || 'Export')}</h2>
{description}
<div>
<textarea className='cb json' onFocus={ (e) => e.target.select() }>{this.state.exportJson}</textarea>
</div>
<button className={'r dismiss cap'} onClick={InterfaceEvents.hideModal}>{translate('close')}</button>
</div>;
}
}

View File

@@ -0,0 +1,439 @@
import React from 'react';
import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
import InterfaceEvents from '../utils/InterfaceEvents';
import Persist from '../stores/Persist';
import Ships from '../shipyard/Ships';
import Ship from '../shipyard/Ship';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import { Download } from './SvgIcons';
const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n');
const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)');
const mountMap = { 'H': 4, 'L': 3, 'M': 2, 'S': 1, 'U': 0 };
const standardMap = { 'RB': 0, 'TM': 1, 'FH': 2, 'EC': 3, 'PC': 4, 'SS': 5, 'FS': 6 };
const bhMap = { 'lightweight alloy': 0, 'reinforced alloy': 1, 'military grade composite': 2, 'mirrored surface composite': 3, 'reactive surface composite': 4 };
function isEmptySlot(slot) {
return slot.maxClass == this && slot.m === null;
}
function equalsIgnoreCase(str) {
return str.toLowerCase() == this.toLowerCase();
}
function validateBuild(shipId, code, name) {
let shipData = Ships[shipId];
if (!shipData) {
throw '"' + shipId + '" is not a valid Ship Id!';
}
if (typeof name != 'string' || name.length == 0) {
throw shipData.properties.name + ' build "' + name + '" must be a string at least 1 character long!';
}
if (typeof code != 'string' || code.length < 10) {
throw shipData.properties.name + ' build "' + name + '" is not valid!';
}
try {
Serializer.toShip(new Ship(shipId, shipData.properties, shipData.slots), code);
} catch (e) {
throw shipData.properties.name + ' build "' + name + '" is not valid!';
}
}
function detailedJsonToBuild(detailedBuild) {
let ship;
if (!detailedBuild.name) {
throw 'Build Name missing!';
}
if (!detailedBuild.name.trim()) {
throw 'Build Name must be a string at least 1 character long!';
}
try {
ship = Serializer.fromDetailedBuild(detailedBuild);
} catch (e) {
throw detailedBuild.ship + ' Build "' + detailedBuild.name + '": Invalid data';
}
return { shipId: ship.id, name: detailedBuild.name, code: Serializer.fromShip(ship) };
}
export default class ModalImport extends TranslatedComponent {
static propTypes = {
title: React.propTypes.string,
promise: : React.propTypes.func,
data: React.propTypes.oneOfType[React.propTypes.string, React.propTypes.object]
};
constructor(props) {
super(props);
this.state = {
builds: null,
canEdit: true,
comparisons: null,
discounts: null,
errorMsg: null,
importString: null,
importValid: false,
insurance: null
};
this._process = this._process.bind(this);
this._importBackup = this._importBackup.bind(this);
}
_importBackup(importData) {
if (importData.builds && typeof importData.builds == 'object') {
for (let shipId in importData.builds) {
for (let buildName in importData.builds[shipId]) {
validateBuild(shipId, importData.builds[shipId][buildName], buildName);
}
}
this.setState({ builds: importData.builds });
} else {
throw 'builds must be an object!';
}
if (importData.comparisons) {
for (let compName in importData.comparisons) {
let comparison = importData.comparisons[compName];
for (let i = 0, l = comparison.builds.length; i < l; i++) {
let build = comparison.builds[i];
if (!importData.builds[build.shipId] || !importData.builds[build.shipId][build.buildName]) {
throw build.shipId + ' build "' + build.buildName + '" data is missing!';
}
}
}
this.setState({ comparisons: importData.comparisons });
}
if (importData.discounts instanceof Array && importData.discounts.length == 2) {
this.setState({ discounts: importData.discounts });
}
if (typeof importData.insurance == 'string' && importData.insurance.length > 3) {
this.setState({ insurance: importData.insurance });
}
}
_importDetailedArray(importArr) {
let builds = {};
for (let i = 0, l = importArr.length; i < l; i++) {
let build = this._detailedJsonToBuild(importArr[i]);
if (!builds[build.shipId]) {
builds[build.shipId] = {};
}
builds[build.shipId][build.name] = build.code;
}
this.setState({ builds });
}
_importTextBuild(buildStr) {
let buildName = textBuildRegex.exec(buildStr)[1].trim();
let shipName = buildName.toLowerCase();
let shipId = null;
for (let sId in Ships) {
if (Ships[sId].properties.name.toLowerCase() == shipName) {
shipId = sId;
break;
}
}
if (!shipId) {
throw 'No such ship found: "' + buildName + '"';
}
let lines = buildStr.split('\n');
let ship = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots);
ship.buildWith(null);
for (let i = 1; i < lines.length; i++) {
let line = lines[i].trim();
if (!line) { continue; }
if (line.substring(0, 3) == '---') { break; }
let parts = lineRegex.exec(line);
if (!parts) { throw 'Error parsing: "' + line + '"'; }
let typeSize = parts[1];
let cl = parts[2];
let rating = parts[3];
let mount = parts[4];
let missile = parts[5];
let name = parts[6].trim();
let slot, group;
if (isNaN(typeSize)) { // Standard or Hardpoint
if (typeSize.length == 1) { // Hardpoint
let slotClass = mountMap[typeSize];
if (cl > slotClass) { throw cl + rating + ' ' + name + ' exceeds slot size: "' + line + '"'; }
slot = _.find(ship.hardpoints, isEmptySlot, slotClass);
if (!slot) { throw 'No hardpoint slot available for: "' + line + '"'; }
group = _.find(GroupMap, equalsIgnoreCase, name);
let hp = ModuleUtils.findHardpoint(group, cl, rating, group ? null : name, mount, missile);
if (!hp) { throw 'Unknown component: "' + line + '"'; }
ship.use(slot, hp, true);
} else if (typeSize == 'BH') {
let bhId = bhMap[name.toLowerCase()];
if (bhId === undefined) { throw 'Unknown bulkhead: "' + line + '"'; }
ship.useBulkhead(bhId, true);
} else if (standardMap[typeSize] != undefined) {
let standardIndex = standardMap[typeSize];
if (ship.standard[standardIndex].maxClass < cl) { throw name + ' exceeds max class for the ' + ship.name; }
ship.use(ship.standard[standardIndex], cl + rating, ModuleUtils.standard(standardIndex, cl + rating), true);
} else {
throw 'Unknown component: "' + line + '"';
}
} else {
if (cl > typeSize) { throw cl + rating + ' ' + name + ' exceeds slot size: "' + line + '"'; }
slot = _.find(ship.internal, isEmptySlot, typeSize);
if (!slot) { throw 'No internal slot available for: "' + line + '"'; }
group = _.find(GroupMap, equalsIgnoreCase, name);
let intComp = ModuleUtils.findInternal(group, cl, rating, group ? null : name);
if (!intComp) { throw 'Unknown component: "' + line + '"'; }
ship.use(slot, intComp.id, intComp);
}
}
let builds = {};
builds[shipId] = {};
builds[shipId]['Imported ' + buildName] = Serializer.fromShip(ship);
this.setState({ builds });
}
_validateImport() {
let importData = null;
let importString = $scope.importString.trim();
this.setState({
builds: null,
comparisons: null,
discounts: null,
errorMsg: null,
importValid: false,
insurance: null
});
if (!importString) {
return;
}
try {
if (textBuildRegex.test(importString)) { // E:D Shipyard build text
importTextBuild(importString);
} else { // JSON Build data
importData = angular.fromJson($scope.importString);
if (!importData || typeof importData != 'object') {
throw 'Must be an object or array!';
}
if (importData instanceof Array) { // Must be detailed export json
this._importDetailedArray(importData);
} else if (importData.ship && typeof importData.name !== undefined) { // Using JSON from a single ship build export
this._importDetailedArray([importData]); // Convert to array with singleobject
} else { // Using Backup JSON
this._importBackup(importData);
}
}
} catch (e) {
this.setState({ errorMsg: (typeof e == 'string') ? e : 'Cannot Parse the data!' });
return;
}
this.setState({ importValid: true });
};
_process() {
let builds = null, comparisons = null;
if (this.state.builds) {
builds = $scope.builds;
for (let shipId in builds) {
for (let buildName in builds[shipId]) {
let code = builds[shipId][buildName];
// Update builds object such that orginal name retained, but can be renamed
builds[shipId][buildName] = {
code: code,
useName: buildName
};
}
}
}
if (this.state.comparisons) {
let comparisons = $scope.comparisons;
for (let name in comparisons) {
comparisons[name].useName = name;
}
}
this.setState({ processed: true, builds, comparisons });
};
_import() {
if (this.state.builds) {
let builds = this.state.builds;
for (let shipId in builds) {
for (let buildName in builds[shipId]) {
let build = builds[shipId][buildName];
let name = build.useName.trim();
if (name) {
Persist.saveBuild(shipId, name, build.code);
}
}
}
}
if (this.state.comparisons) {
let comparisons = this.state.comparisons;
for (let comp in comparisons) {
let comparison = comparisons[comp];
let useName = comparison.useName.trim();
if (useName) {
Persist.saveComparison(useName, comparison.builds, comparison.facets);
}
}
}
if (this.state.discounts) {
Persist.setDiscount((this.state.discounts);
}
if (this.state.insurance) {
Persist.setInsurance(this.state.insurance);
}
InterfaceEvents.hideModal();
};
componentWillMount() {
if (this.props.importingBuilds) {
this.setState({ builds: this.props.importingBuilds, canEdit : false});
this._process();
}
}
render() {
let translate = this.context.language.translate;
let importStage;
if (this.state.processed) {
importStage = (
<div>
<textarea className='cb json' onCange={this.validateImport.bind(this)} placeholder={translate('PHRASE_IMPORT') | translate} />
<button className='l cap' onClick={this.process.bind(this)} disabled={!this.state.importValid} >{translate('proceed')}</button>
<div className='l warning' style={{ marginLeft:'3em' }}>{this.state.errorMsg}</div>
</div>
);
} else {
let comparisonTable, edit, buildRows = [];
if (this.state.comparisons) {
let comparisonRows = [];
for (let name in comparisons) {
let comparison = comparisons[name];
let hasComparison = Persist.hasComparison(name);
comparisonRows.push(
<tr key={name} className='cb'>
<td>
<input type='text' value={comparison.useName}/>
</td>
<td style={{ textAlign:'center' }} className={ cn({ warning: hasComparison, disabled: comparison.useName == '' }) }>
<span>{translate(comparison.useName == '' ? 'skip' : (hasComparison ? 'overwrite' : 'create'))}></span>
</td>
</tr>
);
comparisonTable = (
<table className='l' style={{ overflow:'hidden', margin: '1em 0', width: '100%'}} >
<thead>
<tr>
<th style={{ textAlign:'left' }}>{translate('comparison')}</th>
<th >{translate('action')}</th>
</tr>
</thead>
<tbody>
{comparisonRows}
</tbody>
</table>
);
}
if(this.state.canEdit) {
edit = <button className='l cap' style={{ marginLeft: '2em' }} ng-click={() => this.setState({processed: false})}>{translate('edit data')}</button>
}
let builds = this.state.builds;
for (let shipId in builds) {
let shipBuilds = builds[shipId];
for (let buildName in shipBuilds) {
let b = shipBuilds[buildName];
let hasBuild = Persist.hasBuild(shipId, b.useName);
buildRows.push(
<tr className='cb'>
<td>{{Ships[shipId].properties.name}}</td>
<td><input type='text' value={b.useName}/></td>
<td style={{ textAlign: 'center' }} className={cn({ warning: hasBuild, disabled: b.useName == ''})}>
<span>{translate(b.useName == '' ? 'skip' : (hasBuild ? 'overwrite' : 'create'))}></span>
</td>
</tr>
);
}
}
importStage = (
<div>
<table className='l' style='overflow:hidden;margin: 1em 0; width: 100%;'>
<thead>
<tr>
<th style='text-align:left' >{translate('ship')}</th>
<th style='text-align:left' >{translate('build name')}</th>
<th >{translate('action')}</th>
</tr>
</thead>
<tbody>
{buildRows}
</tbody>
</table>
{comparisonTable}
<button className='cl l' onClick={this._import}><Download/> {translate('import')}</button>
{edit}
</div>
);
}
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2 >{translate('import')}</h2>
{importStage}
<button className={'r dismiss cap'} onClick={InterfaceEvents.hideModal}>{translate('close')}</button>
</div>;
}
}

View File

@@ -0,0 +1,41 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import InterfaceEvents from '../utils/InterfaceEvents';
export default class ModalPermalink extends TranslatedComponent {
static propTypes = {
url: React.propTypes.string.isRequired
};
constructor(props) {
super(props);
this.state = {
shortenedUrl: 'Shortening...'
};
}
componentWillMount(){
ShortenUrl(this.props.url,
(shortenedUrl) => this.setState({ shortenedUrl }),
(error) => this.setState({ shortenedUrl: 'Error - ' + e.statusText })
);
}
render() {
let translate = this.context.language.translate;
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2>{translate('permalink')}</h2>
<br/>
<h3>{translate('URL')}</h3>
<input value={this.props.url} size=40 onFocus={ (e) => e.target.select() }/>
<br/><br/>
<h3 >{translate('shortened')}</h3>
<input value={this.state.shortenedUrl} size=25 onFocus={ (e) => e.target.select() }/>
<br/><br/>
<button className={'r dismiss cap'} onClick={InterfaceEvents.hideModal}>{translate('close')}</button>
</div>;
}
}

View File

@@ -0,0 +1,94 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames';
import { SizeMap } from '../shipyard/Constants';
import { Warning } from './SvgIcons';
import shallowEqual from '../utils/shallowEqual';
export default class ShipSummaryTable extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired
}
shouldComponentUpdate() {
return true;
}
render() {
let ship = this.props.ship;
let language = this.context.language;
let translate = language.translate;
let u = language.units;
let formats = language.formats;
let round = formats.round;
let int = formats.int;
let armourDetails = null;
if (ship.armourMultiplier > 1 || ship.armourAdded) {
armourDetails = <u>({
(ship.armourMultiplier > 1 ? formats.rPct(ship.armourMultiplier) : '')
+ (ship.armourAdded ? ' + ' + ship.armourAdded : '')
})</u>;
}
return (
<div id='summary'>
<table id='summaryTable'>
<thead>
<tr className='main'>
<th rowSpan={2}>{translate('size')}</th>
<th rowSpan={2}>{translate('agility')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canThrust() }) }>{translate('speed')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canBoost() }) }>{translate('boost')}</th>
<th rowSpan={2}>{translate('DPS')}</th>
<th rowSpan={2}>{translate('armour')}</th>
<th rowSpan={2}>{translate('shields')}</th>
<th colSpan={3}>{translate('mass')}</th>
<th rowSpan={2}>{translate('cargo')}</th>
<th rowSpan={2}>{translate('fuel')}</th>
<th colSpan={3}>{translate('jump range')}</th>
<th colSpan={3}>{translate('total range')}</th>
<th rowSpan={2}>{translate('lock factor')}</th>
</tr>
<tr>
<th className='lft'>{translate('hull')}</th>
<th>{translate('unladen')}</th>
<th>{translate('laden')}</th>
<th className='lft'>{translate('max')}</th>
<th>{translate('full tank')}</th>
<th>{translate('laden')}</th>
<th className='lft'>{translate('jumps')}</th>
<th>{translate('unladen')}</th>
<th>{translate('laden')}</th>
</tr>
</thead>
<tbody>
<tr>
<td className='cap'>{translate(SizeMap[ship.class])}</td>
<td>{ship.agility}/10</td>
<td>{ ship.canThrust() ? <span>{int(ship.topSpeed)} {u.ms}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td>{ ship.canBoost() ? <span>{int(ship.topBoost)} {u.ms}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td>{round(ship.totalDps)}</td>
<td>{int(ship.armour)} {armourDetails}</td>
<td>{int(ship.shieldStrength)} {u.MJ} { ship.shieldMultiplier > 1 && ship.shieldStrength > 0 ? <u>({formats.rPct(ship.shieldMultiplier)})</u> : null }</td>
<td>{ship.hullMass} {u.T}</td>
<td>{round(ship.unladenMass)} {u.T}</td>
<td>{round(ship.ladenMass)} {u.T}</td>
<td>{round(ship.cargoCapacity)} {u.T}</td>
<td>{round(ship.fuelCapacity)} {u.T}</td>
<td>{round(ship.unladenRange)} {u.LY}</td>
<td>{round(ship.fullTankRange)} {u.LY}</td>
<td>{round(ship.ladenRange)} {u.LY}</td>
<td>{round(ship.maxJumpCount)}</td>
<td>{round(ship.unladenTotalRange)} {u.LY}</td>
<td>{round(ship.ladenTotalRange)} {u.LY}</td>
<td>{ship.masslock}</td>
</tr>
</tbody>
</table>
</div>
);
}
}

View File

@@ -0,0 +1,61 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames';
import AvailableModulesMenu from './AvailableModulesMenu';
import { contextMenuHandler } from '../utils/InterfaceEvents';
export default class Slot extends TranslatedComponent {
static propTypes = {
modules: React.PropTypes.oneOfType([ React.PropTypes.object, React.PropTypes.array ]).isRequired,
onSelect: React.PropTypes.func.isRequired,
onOpen: React.PropTypes.func.isRequired,
size: React.PropTypes.number.isRequired,
selected: React.PropTypes.bool,
m: React.PropTypes.object,
shipMass: React.PropTypes.number,
warning: React.PropTypes.func,
};
getClassNames() {
return null;
}
getSize() {
return this.props.size;
}
render() {
let language = this.context.language;
let translate = language.translate;
let m = this.props.m;
let slotDetails, menu;
if (m) {
slotDetails = this.getSlotDetails(m, translate, language.formats, language.units); // Must be implemented by sub classes
} else {
slotDetails = <div className={'empty'}>{translate('empty')}</div>;
}
if (this.props.selected) {
menu = <AvailableModulesMenu
className={this.getClassNames()}
modules={this.props.modules}
shipMass={this.props.shipMass}
m={m}
onSelect={this.props.onSelect}
warning={this.props.warning}
/>;
}
return (
<div className={cn('slot', {selected: this.props.selected})} onClick={this.props.onOpen} onContextMenu={ this.contextmenu === false ? null : contextMenuHandler(this.props.onSelect.bind(null, null))}>
<div className={'details'}>
<div className={'sz'}>{this.getSize(translate)}</div>
{slotDetails}
</div>
{menu}
</div>
);
}
}

View File

@@ -0,0 +1,73 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import InterfaceEvents from '../utils/InterfaceEvents';
import { Equalizer } from '../components/SvgIcons';
import cn from 'classnames';
export default class SlotSection extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired
};
constructor(props, context, sectionId, sectionName) {
super(props);
this.sectionId = sectionId;
this.sectionName = sectionName;
this._getSlots = this._getSlots.bind(this);
this._selectModule = this._selectModule.bind(this);
this._getSectionMenu = this._getSectionMenu.bind(this);
this.state = {
currentMenu: null
}
}
// Must be implemented by subclasses:
// _getSlots()
// _getSectionMenu()
_openMenu(menu) {
this.setState({ currentMenu: menu });
InterfaceEvents.closeAll(menu);
}
_closeMenu() {
if (this.state.currentMenu) {
this.setState({ currentMenu: null });
}
}
_selectModule(index, slot, m) {
this.props.ship.use(slot, m);
this._closeMenu();
}
componentWillReceiveProps(nextProps, nextContext) {
this.setState({ currentMenu: null });
}
componentWillMount() {
this.closeAllListener = InterfaceEvents.addListener('closeAll', this._closeMenu.bind(this));
}
componentWillUnmount() {
this.closeAllListener.remove();
}
render() {
let translate = this.context.language.translate;
let sectionMenuOpened = this.state.currentMenu === this.sectionName;
return (
<div id={this.sectionId} className={'group'}>
<div className={cn('section-menu', {selected: sectionMenuOpened})} onClick={this._openMenu.bind(this, this.sectionName)}>
<h1>{translate(this.sectionName)} <Equalizer/></h1>
{sectionMenuOpened ? this._getSectionMenu(translate) : null }
</div>
{this._getSlots()}
</div>
);
}
}

View File

@@ -0,0 +1,60 @@
import React from 'react';
import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
import AvailableModulesMenu from './AvailableModulesMenu';
export default class StandardSlot extends TranslatedComponent {
static propTypes = {
slot: React.PropTypes.object,
modules: React.PropTypes.oneOfType([ React.PropTypes.object, React.PropTypes.array ]).isRequired,
onSelect: React.PropTypes.func.isRequired,
onOpen: React.PropTypes.func.isRequired,
selected: React.PropTypes.bool,
shipMass: React.PropTypes.number,
warning: React.PropTypes.func,
};
render() {
let { translate, formats, units } = this.context.language;
let slot = this.props.slot
let m = slot.m;
let classRating = m.class + m.rating;
let menu;
if (this.props.selected) {
menu = <AvailableModulesMenu
modules={this.props.modules}
shipMass={this.props.shipMass}
m={m}
onSelect={this.props.onSelect}
warning={this.props.warning}
/>;
}
return (
<div className={cn('slot', {selected: this.props.selected})} onClick={this.props.onOpen}>
<div className={'details'}>
<div className={'sz'}>{slot.maxClass}</div>
<div>
<div className={'l'}>{classRating + ' ' + translate(m.grp)}</div>
<div className={'r'}>{m.mass || m.capacity}{units.T}</div>
<div className={'cb'}>
{ m.optmass ? <div className={'l'}>{translate('optimal mass') + ': '}{m.optmass}{units.T}</div> : null }
{ m.maxmass ? <div className={'l'}>{translate('max mass') + ': '}{m.maxmass}{units.T}</div> : null }
{ m.range ? <div className={'l'}>{translate('range')} {m.range}{units.km}</div> : null }
{ m.time ? <div className={'l'}>{translate('time')}: {formats.time(m.time)}</div> : null }
{ m.eff ? <div className={'l'}>{translate('efficiency')}: {m.eff}</div> : null }
{ m.pGen ? <div className={'l'}>{translate('power')}: {m.pGen}{units.MW}</div> : null }
{ m.maxfuel ? <div className={'l'}>{translate('max') + ' ' + translate('fuel') + ': '}{m.maxfuel}{units.T}</div> : null }
{ m.weaponcapacity ? <div className={'l'}>{translate('WEP')}: {m.weaponcapacity}{units.MJ} / {m.weaponrecharge}{units.MW}</div> : null }
{ m.systemcapacity ? <div className={'l'}>{translate('SYS')}: {m.systemcapacity}{units.MJ} / {m.systemrecharge}{units.MW}</div> : null }
{ m.enginecapacity ? <div className={'l'}>{translate('ENG')}: {m.enginecapacity}{units.MJ} / {m.enginerecharge}{units.MW}</div> : null }
</div>
</div>
</div>
{menu}
</div>
);
}
}

View File

@@ -0,0 +1,167 @@
import React from 'react';
import SlotSection from './SlotSection';
import StandardSlot from './StandardSlot';
import cn from 'classnames';
import { ArmourMultiplier } from '../shipyard/Constants';
export default class StandardSlotSection extends SlotSection {
constructor(props, context) {
super(props, context, 'standard', 'standard');
this._optimizeStandard = this._optimizeStandard.bind(this);
this._optimizeCargo = this._optimizeCargo.bind(this);
this._optimizeExplorer = this._optimizeExplorer.bind(this);
}
_fill(rating) {
}
_optimizeStandard() {
}
_optimizeCargo() {
}
_optimizeExplorer() {
}
_selectBulkhead(bulkheadIndex) {
this.props.ship.useBulkhead(bulkheadIndex);
this._closeMenu();
}
_getSlots() {
let { formats, translate, units } = this.context.language;
let slots = new Array(8);
let open = this._openMenu;
let select = this._selectModule;
let selBulkhead = this._selectBulkhead;
let ship = this.props.ship
let st = ship.standard;
let avail = ship.getAvailableModules().standard;
let bulkheads = ship.bulkheads;
let bulkheadIndex = bulkheads.id;
let currentMenu = this.state.currentMenu;
slots[0] = (
<div key='bh' className={cn('slot', {selected: currentMenu === bulkheads})} onClick={open.bind(this, bulkheads)}>
<div className={'details'}>
<div className={'sz'}>8</div>
<div>
<div className={'l'}>{translate('bh')}</div>
<div className={'r'}>{bulkheads.m.mass}{units.T}</div>
<div className={'cl l'}>{translate(bulkheads.m.name)}</div>
</div>
</div>
{currentMenu === bulkheads &&
<div className='select' onClick={ e => e.stopPropagation() }>
<ul>
<li onClick={selBulkhead.bind(this, 0)} className={cn('lc', { active: bulkheads.id=='0' })}>{translate('Lightweight Alloy')}</li>
<li onClick={selBulkhead.bind(this, 1)} className={cn('lc', { active: bulkheads.id=='1' })}>{translate('Reinforced Alloy')}</li>
<li onClick={selBulkhead.bind(this, 2)} className={cn('lc', { active: bulkheads.id=='2' })}>{translate('Military Grade Composite')}</li>
<li onClick={selBulkhead.bind(this, 3)} className={cn('lc', { active: bulkheads.id=='3' })}>{translate('Mirrored Surface Composite')}</li>
<li onClick={selBulkhead.bind(this, 4)} className={cn('lc', { active: bulkheads.id=='4' })}>{translate('Reactive Surface Composite')}</li>
</ul>
</div>
}
</div>
);
slots[1] = <StandardSlot
key='pp'
slot={st[0]}
modules={avail[0]}
onOpen={open.bind(this, st[0])}
onSelect={select.bind(this, 1, st[0])}
selected={currentMenu == st[0]}
warning={(m) => m.pGen < ship.powerRetracted}
/>;
slots[2] = <StandardSlot
key='th'
slot={st[1]}
modules={avail[1]}
onOpen={open.bind(this, st[1])}
onSelect={select.bind(this, 2, st[1])}
selected={currentMenu == st[1]}
warning={(m) => m.maxmass < ship.ladenMass}
/>;
slots[3] = <StandardSlot
key='fsd'
slot={st[2]}
modules={avail[2]}
onOpen={open.bind(this, st[2])}
onSelect={select.bind(this, 3, st[2])}
selected={currentMenu == st[2]}
/>;
slots[4] = <StandardSlot
key='ls'
slot={st[3]}
modules={avail[3]}
onOpen={open.bind(this, st[3])}
onSelect={select.bind(this, 4, st[3])}
selected={currentMenu == st[3]}
/>;
slots[5] = <StandardSlot
key='pd'
slot={st[4]}
modules={avail[4]}
onOpen={open.bind(this, st[4])}
onSelect={select.bind(this, 5, st[4])}
selected={currentMenu == st[4]}
warning= {m => m.enginecapacity < ship.boostEnergy}
/>;
slots[6] = <StandardSlot
key='ss'
slot={st[5]}
modules={avail[5]}
onOpen={open.bind(this, st[5])}
onSelect={select.bind(this, 6, st[5])}
selected={currentMenu == st[5]}
warning= {m => m.enginecapacity < ship.boostEnergy}
/>;
slots[7] = <StandardSlot
key='ft'
slot={st[6]}
modules={avail[6]}
onOpen={open.bind(this, st[6])}
onSelect={select.bind(this, 7, st[6])}
selected={currentMenu == st[6]}
warning= {m => m.capacity < st[2].m.maxfuel} // Show warning when fuel tank is smaller than FSD Max Fuel
/>;
return slots;
}
_getSectionMenu(translate) {
let _fill = this._fill;
return <div className='select' onClick={(e) => e.stopPropagation()}>
<ul>
<li className='lc' onClick={this._optimizeStandard}>{translate('Optimize')}</li>
<li className='c' onClick={_fill.bind(this, 'E')}>E</li>
<li className='c' onClick={_fill.bind(this, 'D')}>D</li>
<li className='c' onClick={_fill.bind(this, 'C')}>C</li>
<li className='c' onClick={_fill.bind(this, 'B')}>B</li>
<li className='c' onClick={_fill.bind(this, 'A')}>A</li>
</ul>
<div className='select-group cap'>{translate('builds / roles')}</div>
<ul>
<li className='lc' onClick={this._optimizeCargo}>{translate('Trader')}</li>
<li className='lc' onClick={this._optimizeExplorer}>{translate('Explorer')}</li>
</ul>
</div>;
}
}

View File

@@ -0,0 +1,302 @@
import React from 'react';
import cn from 'classnames';
import shallowEqual from '../utils/shallowEqual';
class SvgIcon extends React.Component {
shouldComponentUpdate(nextProps) { return !shallowEqual(this.props, nextProps); }
svg() { return null; }
viewBox() { return '0 0 32 32'; }
render() {
return (
<svg className={cn('icon', this.props.className)} style={this.props.style} viewBox={this.viewBox()}>
{this.svg()}
</svg>
);
}
}
export class Bin extends SvgIcon {
svg() {
return <g>
<path d='M4 10v20c0 1.1 0.9 2 2 2h18c1.1 0 2-0.9 2-2v-20h-22zM10 28h-2v-14h2v14zM14 28h-2v-14h2v14zM18 28h-2v-14h2v14zM22 28h-2v-14h2v14z'/>
<path d='M26.5 4h-6.5v-2.5c0-0.825-0.675-1.5-1.5-1.5h-7c-0.825 0-1.5 0.675-1.5 1.5v2.5h-6.5c-0.825 0-1.5 0.675-1.5 1.5v2.5h26v-2.5c0-0.825-0.675-1.5-1.5-1.5zM18 4h-6v-1.975h6v1.975z'/>
</g>;
}
}
export class CoriolisLogo extends SvgIcon {
svg() {
return <g transform='translate(1,1)'>
<path stroke='#ff3b00' transform='rotate(45 15 15)' d='m4,4 l 11,-4 l 11,4 l 4,11 l -4,11 l -11,4 l -11,-4 l -4,-11 l 4,-11 l 22,0 l 0,22 l -22,0 z' strokeWidth='1' fill='#000000'/>
<rect height='3' width='10' y='13.5' x='10' strokeWidth='1' stroke='#ff3b00'/>
</g>;
}
}
export class Download extends SvgIcon {
svg() {
return <path d='M16 18l8-8h-6v-8h-4v8h-6zM23.273 14.727l-2.242 2.242 8.128 3.031-13.158 4.907-13.158-4.907 8.127-3.031-2.242-2.242-8.727 3.273v8l16 6 16-6v-8z'/>;
}
}
export class Eddb extends SvgIcon {
render() {
return <svg className={cn(this.props.className)} style={this.props.style} viewBox='0 0 90 32'>
<path d='M19.1,25.2c0.3,0,0.6,0.1,0.7,0.2c0.2,0.1,0.3,0.3,0.4,0.4c0.1,0.2,0.2,0.4,0.2,0.6v3.3c0,0.3-0.1,0.6-0.2,0.7c-0.1,0.2-0.3,0.3-0.4,0.3c-0.2,0.1-0.4,0.2-0.6,0.1H3.6c-0.3,0-0.6-0.1-0.7-0.2c-0.2-0.1-0.3-0.3-0.3-0.4c-0.1-0.2-0.2-0.4-0.1-0.6V10.2c0-0.3,0.1-0.5,0.2-0.7C2.7,9.4,2.9,9.3,3,9.2C3.2,9.1,3.4,9,3.6,9h15.5c0.3,0,0.6,0.1,0.7,0.2c0.2,0.1,0.3,0.3,0.4,0.4c0.1,0.2,0.2,0.4,0.2,0.6V22c0,0.3-0.1,0.6-0.2,0.7c-0.1,0.2-0.3,0.3-0.4,0.3c-0.2,0.1-0.4,0.2-0.6,0.1h-6.8v-6.8c0.3-0.2,0.6-0.4,0.8-0.7c0.2-0.3,0.3-0.7,0.3-1c0-0.6-0.2-1.1-0.6-1.4c-0.4-0.4-0.9-0.6-1.4-0.6c-0.5,0-1,0.2-1.4,0.6c-0.4,0.4-0.6,0.9-0.6,1.4c0,0.8,0.3,1.4,1,1.8v8.7H19.1z'/>
<path d='M24.6,29.7V10.2c0-0.2,0-0.4,0.1-0.6c0.1-0.1,0.2-0.3,0.3-0.4C25.3,9.1,25.5,9,25.8,9h5.5c0.2,0,0.4,0.1,0.6,0.2c0.1,0.1,0.3,0.2,0.4,0.3c0.1,0.1,0.2,0.4,0.2,0.7v13.2c-0.7,0.4-1,1-1,1.8c0,0.5,0.2,1,0.6,1.4c0.4,0.4,0.9,0.6,1.4,0.6c0.6,0,1.1-0.2,1.4-0.6c0.4-0.4,0.6-0.9,0.6-1.4c0-0.4-0.1-0.8-0.3-1.1c-0.2-0.3-0.4-0.5-0.8-0.7V2.3c0-0.2,0-0.4,0.1-0.6c0.1-0.1,0.2-0.3,0.3-0.4C35.2,1.1,35.4,1,35.8,1h5.5c0.2,0,0.4,0.1,0.6,0.2c0.1,0.1,0.3,0.2,0.4,0.4c0.1,0.2,0.2,0.4,0.2,0.7v27.4c0,0.2-0.1,0.4-0.2,0.6c-0.1,0.1-0.2,0.3-0.4,0.4c-0.2,0.1-0.4,0.2-0.7,0.2H25.8c-0.2,0-0.4,0-0.6-0.1c-0.1-0.1-0.3-0.2-0.4-0.3C24.7,30.3,24.6,30,24.6,29.7z'/>
<path d='M46.9,29.7V10.2c0-0.2,0-0.4,0.1-0.6c0.1-0.1,0.2-0.3,0.3-0.4C47.5,9.1,47.7,9,48.1,9h5.5c0.2,0,0.4,0.1,0.6,0.2c0.1,0.1,0.3,0.2,0.4,0.3c0.1,0.1,0.2,0.4,0.2,0.7v13.2c-0.7,0.4-1,1-1,1.8c0,0.5,0.2,1,0.6,1.4c0.4,0.4,0.9,0.6,1.4,0.6c0.6,0,1.1-0.2,1.4-0.6c0.4-0.4,0.6-0.9,0.6-1.4c0-0.4-0.1-0.8-0.3-1.1c-0.2-0.3-0.4-0.5-0.8-0.7V2.3c0-0.2,0-0.4,0.1-0.6c0.1-0.1,0.2-0.3,0.3-0.4C57.4,1.1,57.7,1,58,1h5.5c0.2,0,0.4,0.1,0.6,0.2c0.1,0.1,0.3,0.2,0.4,0.4c0.1,0.2,0.2,0.4,0.2,0.7v27.4c0,0.2-0.1,0.4-0.2,0.6c-0.1,0.1-0.2,0.3-0.4,0.4s-0.4,0.2-0.7,0.2H48.1c-0.2,0-0.4,0-0.6-0.1c-0.1-0.1-0.3-0.2-0.4-0.3C46.9,30.3,46.9,30,46.9,29.7z'/>
<path d='M87,29.7c0,0.3-0.1,0.6-0.2,0.7c-0.1,0.2-0.3,0.3-0.4,0.3c-0.2,0.1-0.4,0.2-0.6,0.1H70.3c-0.3,0-0.6-0.1-0.7-0.2s-0.3-0.3-0.3-0.4c-0.1-0.2-0.2-0.4-0.1-0.6V2.3c0-0.3,0.1-0.6,0.2-0.7c0.1-0.2,0.3-0.3,0.4-0.4C69.9,1.1,70.1,1,70.3,1h5.5c0.3,0,0.6,0.1,0.7,0.2c0.2,0.1,0.3,0.3,0.4,0.4c0.1,0.2,0.2,0.4,0.2,0.6v21.2c-0.7,0.4-1,1-1,1.8c0,0.5,0.2,1,0.6,1.4c0.4,0.4,0.8,0.6,1.4,0.6c0.6,0,1.1-0.2,1.4-0.6c0.4-0.4,0.6-0.9,0.6-1.4c0-0.4-0.1-0.8-0.3-1.1c-0.2-0.3-0.4-0.5-0.8-0.7V10.2c0-0.3,0.1-0.5,0.2-0.7c0.1-0.1,0.3-0.3,0.4-0.3C79.8,9.1,80,9,80.2,9h5.5c0.3,0,0.6,0.1,0.7,0.2c0.2,0.1,0.3,0.3,0.4,0.4C87,9.8,87,10,87,10.2V29.7z'/>
</svg>;
}
}
export class Embed extends SvgIcon {
svg() {
return <g>
<path d='M18 23l3 3 10-10-10-10-3 3 7 7z'/>
<path d='M14 9l-3-3-10 10 10 10 3-3-7-7z'/>
</g>;
}
}
export class Equalizer extends SvgIcon {
viewBox () { return '0 0 1024 1024'; }
svg() {
return <g>
<path d='M448 128v-16c0-26.4-21.6-48-48-48h-160c-26.4 0-48 21.6-48 48v16h-192v128h192v16c0 26.4 21.6 48 48 48h160c26.4 0 48-21.6 48-48v-16h576v-128h-576zM256 256v-128h128v128h-128zM832 432c0-26.4-21.6-48-48-48h-160c-26.4 0-48 21.6-48 48v16h-576v128h576v16c0 26.4 21.6 48 48 48h160c26.4 0 48-21.6 48-48v-16h192v-128h-192v-16zM640 576v-128h128v128h-128zM448 752c0-26.4-21.6-48-48-48h-160c-26.4 0-48 21.6-48 48v16h-192v128h192v16c0 26.4 21.6 48 48 48h160c26.4 0 48-21.6 48-48v-16h576v-128h-576v-16zM256 896v-128h128v128h-128z'/>
</g>;
}
}
export class FloppyDisk extends SvgIcon {
svg() {
return <path d='M28 0h-28v32h32v-28l-4-4zM16 4h4v8h-4v-8zM28 28h-24v-24h2v10h18v-10h2.343l1.657 1.657v22.343z' />;
}
}
export class Fuel extends SvgIcon {
svg() {
return <path d='M16 0c-8.837 0-16 7.163-16 16s7.163 16 16 16 16-7.163 16-16-7.163-16-16-16zM9.464 26.067c0.347-0.957 0.536-1.99 0.536-3.067 0-3.886-2.463-7.197-5.913-8.456 0.319-2.654 1.508-5.109 3.427-7.029 2.267-2.266 5.28-3.515 8.485-3.515s6.219 1.248 8.485 3.515c1.92 1.92 3.108 4.375 3.428 7.029-3.45 1.26-5.913 4.57-5.913 8.456 0 1.077 0.189 2.11 0.536 3.067-1.928 1.258-4.18 1.933-6.536 1.933s-4.608-0.675-6.536-1.933zM17.242 20.031c0.434 0.109 0.758 0.503 0.758 0.969v2c0 0.55-0.45 1-1 1h-2c-0.55 0-1-0.45-1-1v-2c0-0.466 0.324-0.86 0.758-0.969l0.742-14.031h1l0.742 14.031z' />;
}
}
export class GitHub extends SvgIcon {
viewBox() { return '0 0 1024 1024' };
svg() {
return <path d='M512 0C229.252 0 0 229.25199999999995 0 512c0 226.251 146.688 418.126 350.155 485.813 25.593 4.686 34.937-11.125 34.937-24.626 0-12.188-0.469-52.562-0.718-95.314-128.708 23.46-161.707-31.541-172.469-60.373-5.525-14.809-30.407-60.249-52.398-72.263-17.988-9.828-43.26-33.237-0.917-33.735 40.434-0.476 69.348 37.308 78.471 52.75 45.938 77.749 119.876 55.627 148.999 42.5 4.654-32.999 17.902-55.627 32.501-68.373-113.657-12.939-233.22-56.875-233.22-253.063 0-55.94 19.968-101.561 52.658-137.404-5.22-12.999-22.844-65.095 5.063-135.563 0 0 42.937-13.749 140.811 52.501 40.811-11.406 84.594-17.031 128.124-17.22 43.499 0.188 87.314 5.874 128.188 17.28 97.689-66.311 140.686-52.501 140.686-52.501 28 70.532 10.375 122.564 5.124 135.499 32.811 35.844 52.626 81.468 52.626 137.404 0 196.686-119.751 240-233.813 252.686 18.439 15.876 34.748 47.001 34.748 94.748 0 68.437-0.686 123.627-0.686 140.501 0 13.625 9.312 29.561 35.25 24.562C877.436 929.998 1024 738.126 1024 512 1024 229.25199999999995 794.748 0 512 0z' />;
}
}
export class Infinite extends SvgIcon {
svg() {
return <path d='M24.5 23.5c-2.003 0-3.887-0.78-5.303-2.197l-3.197-3.196-3.196 3.196c-1.417 1.417-3.3 2.197-5.303 2.197s-3.887-0.78-5.304-2.197c-1.417-1.417-2.197-3.3-2.197-5.303s0.78-3.887 2.197-5.304c1.417-1.417 3.3-2.197 5.304-2.197s3.887 0.78 5.303 2.197l3.196 3.196 3.196-3.196c1.417-1.417 3.3-2.197 5.303-2.197s3.887 0.78 5.303 2.197c1.417 1.417 2.197 3.3 2.197 5.304s-0.78 3.887-2.197 5.303c-1.416 1.417-3.3 2.197-5.303 2.197zM21.304 19.197c0.854 0.853 1.989 1.324 3.196 1.323s2.342-0.47 3.196-1.324c0.854-0.854 1.324-1.989 1.324-3.196s-0.47-2.342-1.324-3.196c-0.854-0.854-1.989-1.324-3.196-1.324s-2.342 0.47-3.196 1.324l-3.196 3.196 3.196 3.197zM7.5 11.48c-1.207 0-2.342 0.47-3.196 1.324s-1.324 1.989-1.324 3.196c0 1.207 0.47 2.342 1.324 3.196s1.989 1.324 3.196 1.324c1.207 0 2.342-0.47 3.196-1.324l3.196-3.196-3.196-3.196c-0.854-0.854-1.989-1.324-3.196-1.324v0z'/>;
}
}
export class Info extends SvgIcon {
svg() {
return <g>
<path d='M14 9.5c0-0.825 0.675-1.5 1.5-1.5h1c0.825 0 1.5 0.675 1.5 1.5v1c0 0.825-0.675 1.5-1.5 1.5h-1c-0.825 0-1.5-0.675-1.5-1.5v-1z'/>
<path d='M20 24h-8v-2h2v-6h-2v-2h6v8h2z'/>
<path d='M16 0c-8.837 0-16 7.163-16 16s7.163 16 16 16 16-7.163 16-16-7.163-16-16-16zM16 29c-7.18 0-13-5.82-13-13s5.82-13 13-13 13 5.82 13 13-5.82 13-13 13z'/>
</g>;
}
}
export class LinkIcon extends SvgIcon {
svg() {
return <g>
<path d='M13.757 19.868c-0.416 0-0.832-0.159-1.149-0.476-2.973-2.973-2.973-7.81 0-10.783l6-6c1.44-1.44 3.355-2.233 5.392-2.233s3.951 0.793 5.392 2.233c2.973 2.973 2.973 7.81 0 10.783l-2.743 2.743c-0.635 0.635-1.663 0.635-2.298 0s-0.635-1.663 0-2.298l2.743-2.743c1.706-1.706 1.706-4.481 0-6.187-0.826-0.826-1.925-1.281-3.094-1.281s-2.267 0.455-3.094 1.281l-6 6c-1.706 1.706-1.706 4.481 0 6.187 0.635 0.635 0.635 1.663 0 2.298-0.317 0.317-0.733 0.476-1.149 0.476z'/>
<path d='M8 31.625c-2.037 0-3.952-0.793-5.392-2.233-2.973-2.973-2.973-7.81 0-10.783l2.743-2.743c0.635-0.635 1.664-0.635 2.298 0s0.635 1.663 0 2.298l-2.743 2.743c-1.706 1.706-1.706 4.481 0 6.187 0.826 0.826 1.925 1.281 3.094 1.281s2.267-0.455 3.094-1.281l6-6c1.706-1.706 1.706-4.481 0-6.187-0.635-0.635-0.635-1.663 0-2.298s1.663-0.635 2.298 0c2.973 2.973 2.973 7.81 0 10.783l-6 6c-1.44 1.44-3.355 2.233-5.392 2.233z'/>
</g>;
}
}
export class NoPower extends SvgIcon {
svg() {
return <path d='M437.020 74.98c-48.353-48.351-112.64-74.98-181.020-74.98s-132.667 26.629-181.020 74.98c-48.351 48.353-74.98 112.64-74.98 181.020s26.629 132.667 74.98 181.020c48.353 48.351 112.64 74.98 181.020 74.98s132.667-26.629 181.020-74.98c48.351-48.353 74.98-112.64 74.98-181.020s-26.629-132.667-74.98-181.020zM448 256c0 41.407-13.177 79.794-35.556 111.19l-267.633-267.634c31.396-22.379 69.782-35.556 111.189-35.556 105.869 0 192 86.131 192 192zM64 256c0-41.407 13.177-79.793 35.556-111.189l267.635 267.634c-31.397 22.378-69.784 35.555-111.191 35.555-105.869 0-192-86.131-192-192z'/>;
}
}
export class Notification extends SvgIcon {
svg() {
return <path d='M16 3c-3.472 0-6.737 1.352-9.192 3.808s-3.808 5.72-3.808 9.192c0 3.472 1.352 6.737 3.808 9.192s5.72 3.808 9.192 3.808c3.472 0 6.737-1.352 9.192-3.808s3.808-5.72 3.808-9.192c0-3.472-1.352-6.737-3.808-9.192s-5.72-3.808-9.192-3.808zM16 0v0c8.837 0 16 7.163 16 16s-7.163 16-16 16c-8.837 0-16-7.163-16-16s7.163-16 16-16zM14 22h4v4h-4zM14 6h4v12h-4z'/>;
}
}
export class Power extends SvgIcon {
svg() {
return <path d='M192 0l-192 256h192l-128 256 448-320h-256l192-192z'/>;
}
}
export class Question extends SvgIcon {
svg() {
return <path d='M14 22h4v4h-4zM22 8c1.105 0 2 0.895 2 2v6l-6 4h-4v-2l6-4v-2h-10v-4h12zM16 3c-3.472 0-6.737 1.352-9.192 3.808s-3.808 5.72-3.808 9.192c0 3.472 1.352 6.737 3.808 9.192s5.72 3.808 9.192 3.808c3.472 0 6.737-1.352 9.192-3.808s3.808-5.72 3.808-9.192c0-3.472-1.352-6.737-3.808-9.192s-5.72-3.808-9.192-3.808zM16 0v0c8.837 0 16 7.163 16 16s-7.163 16-16 16c-8.837 0-16-7.163-16-16s7.163-16 16-16z'/>;
}
}
export class Reload extends SvgIcon {
svg() {
return <path d='M32 12h-12l4.485-4.485c-2.267-2.266-5.28-3.515-8.485-3.515s-6.219 1.248-8.485 3.515c-2.266 2.267-3.515 5.28-3.515 8.485s1.248 6.219 3.515 8.485c2.267 2.266 5.28 3.515 8.485 3.515s6.219-1.248 8.485-3.515c0.189-0.189 0.371-0.384 0.546-0.583l3.010 2.634c-2.933 3.349-7.239 5.464-12.041 5.464-8.837 0-16-7.163-16-16s7.163-16 16-16c4.418 0 8.418 1.791 11.313 4.687l4.687-4.687v12z'/>;
}
}
export class Warning extends SvgIcon {
svg() {
return <g>
<path d='M16 2.899l13.409 26.726h-26.819l13.409-26.726zM16 0c-0.69 0-1.379 0.465-1.903 1.395l-13.659 27.222c-1.046 1.86-0.156 3.383 1.978 3.383h27.166c2.134 0 3.025-1.522 1.978-3.383h0l-13.659-27.222c-0.523-0.93-1.213-1.395-1.903-1.395v0z'/>
<path d='M18 26c0 1.105-0.895 2-2 2s-2-0.895-2-2c0-1.105 0.895-2 2-2s2 0.895 2 2z'/>
<path d='M16 22c-1.105 0-2-0.895-2-2v-6c0-1.105 0.895-2 2-2s2 0.895 2 2v6c0 1.105-0.895 2-2 2z'/>
</g>;
}
}
export class MountFixed extends SvgIcon {
viewBox () { return '0 0 200 200'; }
svg() {
return <g>
<circle fillOpacity='0' r='70' cy='100' cx='100' strokeWidth='5' />
<line y2='60' x2='101' y1='0' x1='101' strokeWidth='5' />
<line y2='101' x2='200' y1='101' x1='140' strokeWidth='5' />
<line y2='101' x2='60' y1='101' x1='0' strokeWidth='5' />
<line y2='200' x2='101' y1='140' x1='101' strokeWidth='5' />
</g>;
}
}
export class MountGimballed extends SvgIcon {
viewBox () { return '0 0 200 200'; }
svg() {
return <g>
<ellipse ry='25' rx='95' cy='100' cx='100' fillOpacity='0' strokeWidth='5' />
<ellipse ry='95' rx='25' cy='100' cx='100' fillOpacity='0' strokeWidth='5' />
</g>;
}
}
export class MountTurret extends SvgIcon {
viewBox () { return '0 0 200 200'; }
svg() {
return <g>
<line y2='170' x2='162' y1='170' x1='8' strokeWidth='6' />
<path d='m13,138l144,0l0,-50l-27,-40l-90,0l-27,40l0,50z' id='svg_12' fillOpacity='0' strokeWidth='6' />
<line y2='91' x2='200' y1='91' x1='159' strokeWidth='6' />
</g>;
}
}
export class Rocket extends SvgIcon {
svg() {
return <path d='M22 2l-10 10h-6l-6 8c0 0 6.357-1.77 10.065-0.94l-10.065 12.94 13.184-10.255c1.839 4.208-1.184 10.255-1.184 10.255l8-6v-6l10-10 2-10-10 2z'/>;
}
}
export class Hammer extends SvgIcon {
svg() {
return <path d='M31.562 25.905l-9.423-9.423c-0.583-0.583-1.538-0.583-2.121 0l-0.707 0.707-5.75-5.75 9.439-9.439h-10l-4.439 4.439-0.439-0.439h-2.121v2.121l0.439 0.439-6.439 6.439 5 5 6.439-6.439 5.75 5.75-0.707 0.707c-0.583 0.583-0.583 1.538 0 2.121l9.423 9.423c0.583 0.583 1.538 0.583 2.121 0l3.535-3.535c0.583-0.583 0.583-1.538 0-2.121z'/>;
}
}
export class StatsBars extends SvgIcon {
svg() {
return <path d='M0 26h32v4h-32zM4 18h4v6h-4zM10 10h4v14h-4zM16 16h4v8h-4zM22 4h4v20h-4z'/>;
}
}
export class Cogs extends SvgIcon {
svg() {
return <path d='M11.366 22.564l1.291-1.807-1.414-1.414-1.807 1.291c-0.335-0.187-0.694-0.337-1.071-0.444l-0.365-2.19h-2l-0.365 2.19c-0.377 0.107-0.736 0.256-1.071 0.444l-1.807-1.291-1.414 1.414 1.291 1.807c-0.187 0.335-0.337 0.694-0.443 1.071l-2.19 0.365v2l2.19 0.365c0.107 0.377 0.256 0.736 0.444 1.071l-1.291 1.807 1.414 1.414 1.807-1.291c0.335 0.187 0.694 0.337 1.071 0.444l0.365 2.19h2l0.365-2.19c0.377-0.107 0.736-0.256 1.071-0.444l1.807 1.291 1.414-1.414-1.291-1.807c0.187-0.335 0.337-0.694 0.444-1.071l2.19-0.365v-2l-2.19-0.365c-0.107-0.377-0.256-0.736-0.444-1.071zM7 27c-1.105 0-2-0.895-2-2s0.895-2 2-2 2 0.895 2 2-0.895 2-2 2zM32 12v-2l-2.106-0.383c-0.039-0.251-0.088-0.499-0.148-0.743l1.799-1.159-0.765-1.848-2.092 0.452c-0.132-0.216-0.273-0.426-0.422-0.629l1.219-1.761-1.414-1.414-1.761 1.219c-0.203-0.149-0.413-0.29-0.629-0.422l0.452-2.092-1.848-0.765-1.159 1.799c-0.244-0.059-0.492-0.109-0.743-0.148l-0.383-2.106h-2l-0.383 2.106c-0.251 0.039-0.499 0.088-0.743 0.148l-1.159-1.799-1.848 0.765 0.452 2.092c-0.216 0.132-0.426 0.273-0.629 0.422l-1.761-1.219-1.414 1.414 1.219 1.761c-0.149 0.203-0.29 0.413-0.422 0.629l-2.092-0.452-0.765 1.848 1.799 1.159c-0.059 0.244-0.109 0.492-0.148 0.743l-2.106 0.383v2l2.106 0.383c0.039 0.251 0.088 0.499 0.148 0.743l-1.799 1.159 0.765 1.848 2.092-0.452c0.132 0.216 0.273 0.426 0.422 0.629l-1.219 1.761 1.414 1.414 1.761-1.219c0.203 0.149 0.413 0.29 0.629 0.422l-0.452 2.092 1.848 0.765 1.159-1.799c0.244 0.059 0.492 0.109 0.743 0.148l0.383 2.106h2l0.383-2.106c0.251-0.039 0.499-0.088 0.743-0.148l1.159 1.799 1.848-0.765-0.452-2.092c0.216-0.132 0.426-0.273 0.629-0.422l1.761 1.219 1.414-1.414-1.219-1.761c0.149-0.203 0.29-0.413 0.422-0.629l2.092 0.452 0.765-1.848-1.799-1.159c0.059-0.244 0.109-0.492 0.148-0.743l2.106-0.383zM21 15.35c-2.402 0-4.35-1.948-4.35-4.35s1.948-4.35 4.35-4.35 4.35 1.948 4.35 4.35c0 2.402-1.948 4.35-4.35 4.35z'/>;
}
}
export class Switch extends SvgIcon {
svg() {
return <path d='M20 4.581v4.249c1.131 0.494 2.172 1.2 3.071 2.099 1.889 1.889 2.929 4.4 2.929 7.071s-1.040 5.182-2.929 7.071c-1.889 1.889-4.4 2.929-7.071 2.929s-5.182-1.040-7.071-2.929c-1.889-1.889-2.929-4.4-2.929-7.071s1.040-5.182 2.929-7.071c0.899-0.899 1.94-1.606 3.071-2.099v-4.249c-5.783 1.721-10 7.077-10 13.419 0 7.732 6.268 14 14 14s14-6.268 14-14c0-6.342-4.217-11.698-10-13.419zM14 0h4v16h-4z'/>;
}
}
export class StationCoriolis extends SvgIcon {
viewBox () { return '0 0 200 200'; }
svg() {
return <g>
<rect x='73.001' y='94.017' width='53.997' height='11.945'/>
<path d='M10.324,185.445l89.217,14.348l0.458,0.077l89.677-14.43L200,99.998l-10.338-89.765L100,0.129L10.34,10.233 L-0.001,99.986L10.324,185.445z M193.206,99.986L100,191.108L6.795,99.986L100,8.868L193.206,99.986z M6.82,107.775l87.583,85.624 l-78.983-12.702L6.82,107.775z M184.583,180.692l-78.992,12.712l87.587-85.634L184.583,180.692z M193.745,92.746L105.26,6.245l79.339,8.938L193.745,92.746z M15.41,15.185L94.736,6.25L6.255,92.751L15.41,15.185z'/>
</g>;
}
}
export class StationOcellus extends SvgIcon {
viewBox () { return '0 0 200 200'; }
svg() {
return <g>
<path d='M100.002,200C155.139,200,200,155.142,200,100.001c0-55.143-44.861-100.002-99.998-100.002C44.86-0.001-0.002,44.857-0.002,100.001C-0.001,155.142,44.86,200,100.002,200z M100.002,5.574c52.063,0,94.423,42.359,94.423,94.427c0,52.067-42.361,94.422-94.423,94.422c-52.07,0-94.428-42.358-94.428-94.422C5.574,47.933,47.933,5.574,100.002,5.574z'/>
<path d='M100.002,148.557c26.771,0,48.558-21.783,48.558-48.555c0-26.771-21.786-48.556-48.558-48.556c-26.777,0-48.557,21.782-48.557,48.556C51.446,126.778,73.225,148.557,100.002,148.557z M100.002,57.015c23.699,0,42.986,19.283,42.986,42.986c0,23.7-19.282,42.987-42.986,42.987c-23.705,0-42.991-19.282-42.991-42.987C57.011,76.298,76.302,57.015,100.002,57.015z'/>
<rect x='73.404' y='93.985' width='53.197' height='12.033'/>
</g>;
}
}
export class StationOrbis extends SvgIcon {
viewBox () { return '0 0 200 200'; }
svg() {
return <g>
<path d='M100.002,200c55.138,0,99.996-44.861,99.996-100c0-55.141-44.858-100-99.996-100C44.861,0-0.001,44.857-0.001,100C0,155.139,44.861,200,100.002,200z M100.002,194.424c-35.465,0-66.413-19.663-82.552-48.651l44.426-23.388c7.704,13.067,21.888,21.884,38.127,21.884c16.054,0,30.096-8.621,37.853-21.446l44.441,23.389C166.092,174.961,135.282,194.424,100.002,194.424zM100.002,61.306c21.335,0,38.691,17.356,38.691,38.694c0,21.338-17.364,38.691-38.691,38.691c-21.339,0-38.696-17.354-38.696-38.691C61.307,78.662,78.663,61.306,100.002,61.306zM194.422,100c0,14.802-3.427,28.808-9.521,41.287l-44.447-23.4c2.433-5.477,3.812-11.521,3.812-17.89c0-23.578-18.539-42.852-41.8-44.145V5.636C153.392,6.956,194.422,48.762,194.422,100z M96.895,5.655v50.233C73.938,57.491,55.73,76.635,55.73,100c0,6.187,1.286,12.081,3.592,17.434l-44.455,23.402C8.911,128.472,5.571,114.619,5.571,100C5.577,48.972,46.261,7.297,96.895,5.655z'/>
<rect x='73.403' y='93.983' width='53.196' height='12.032'/>
</g>;
}
}
export class StationOutpost extends SvgIcon {
viewBox () { return '0 0 200 200'; }
svg() {
return <g>
<path d='M145.137,59.126h4.498v6.995h5.576V46.556h-5.576v6.994h-4.498V16.328h-5.574v57.667h-15.411v14.824h-7.63v-14.58h-13.044v14.58h-8.295v-14.58H82.138v14.58h-6.573v-14.58H59.072v14.58h-6.573v-14.58H39.458v36.338h13.041V94.391h6.573v16.186h16.493V94.391h6.573v16.186h13.044V94.391h8.295v16.186h13.044V94.391h7.63v40.457l17.634,17.637h13.185v31.182h5.577V73.996H145.14v-14.87H145.137z M154.97,146.907h-10.871l-14.376-14.376V79.57h25.247V146.907z'/>
<rect fill='#999999' x='147.703' y='16.328' width='5.572' height='7.345'/>
<rect fill='#999999' x='131.295' y='16.328' width='5.577' height='7.345'/>
</g>;
}
}
export class Upload extends SvgIcon {
svg() {
return <path d='M14 18h4v-8h6l-8-8-8 8h6zM20 13.5v3.085l9.158 3.415-13.158 4.907-13.158-4.907 9.158-3.415v-3.085l-12 4.5v8l16 6 16-6v-8z'/>;
}
}
export class Loader extends SvgIcon {
viewBox () { return '0 0 40 40'; }
svg() {
return <g className={'loader'}>
<path d='m5,8l5,8l5,-8z' className={'l1 d1'} />
<path d='m5,8l5,-8l5,8z' className={'l1 d2'} />
<path d='m10,0l5,8l5,-8z' className={'l1 d3'} />
<path d='m15,8l5,-8l5,8z' className={'l1 d4'} />
<path d='m20,0l5,8l5,-8z' className={'l1 d5'} />
<path d='m25,8l5,-8l5,8z' className={'l1 d6'} />
<path d='m25,8l5,8l5,-8z' className={'l1 d7'} />
<path d='m30,16l5,-8l5,8z' className={'l1 d8'} />
<path d='m30,16l5,8l5,-8z' className={'l1 d9'} />
<path d='m25,24l5,-8l5,8z' className={'l1 d10'} />
<path d='m25,24l5,8l5,-8z' className={'l1 d11'} />
<path d='m20,32l5,-8l5,8z' className={'l1 d13'} />
<path d='m15,24l5,8l5,-8z' className={'l1 d14'} />
<path d='m10,32l5,-8l5,8z' className={'l1 d15'} />
<path d='m5,24l5,8l5,-8z' className={'l1 d16'} />
<path d='m5,24l5,-8l5,8z' className={'l1 d17'} />
<path d='m0,16l5,8l5,-8z' className={'l1 d18'} />
<path d='m0,16l5,-8l5,8z' className={'l1 d20'} />
<path d='m10,16l5,-8l5,8z' className={'l2 d0'} />
<path d='m15,8l5,8l5,-8z' className={'l2 d3'} />
<path d='m20,16l5,-8l5,8z' className={'l2 d6'} />
<path d='m20,16l5,8l5,-8z' className={'l2 d9'} />
<path d='m15,24l5,-8l5,8z' className={'l2 d12'} />
<path d='m10,16l5,8l5,-8z' className={'l2 d15'} />
</g>;
}
}

View File

@@ -0,0 +1,24 @@
import React from 'react';
import shallowEqual from '../utils/shallowEqual';
export default class TranslatedComponent extends React.Component {
static contextTypes = {
language: React.PropTypes.object.isRequired
}
constructor(props) {
super(props);
this.didContextChange = this.didContextChange.bind(this);
}
didContextChange(nextContext){
return nextContext.language !== this.context.language;
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
return !shallowEqual(this.props, nextProps)
|| !shallowEqual(this.state, nextState)
|| this.didContextChange(nextContext);
}
}

View File

@@ -0,0 +1,64 @@
import React from 'react';
import SlotSection from './SlotSection';
import HardpointSlot from './HardpointSlot';
import cn from 'classnames';
export default class UtilitySlotSection extends SlotSection {
constructor(props, context) {
super(props, context, 'utility', 'utility mounts');
this._empty = this._empty.bind(this);
}
_empty() {
}
_use(grp, rating) {
}
_getSlots() {
let slots = [];
let hardpoints = this.props.ship.hardpoints;
let availableModules = this.props.ship.getAvailableModules();
let currentMenu = this.state.currentMenu;
for (let i = 0, l = hardpoints.length; i < l; i++) {
let h = hardpoints[i];
if (h.maxClass === 0) {
slots.push(<HardpointSlot
key={i}
size={h.maxClass}
modules={availableModules.getHps(h.maxClass)}
onOpen={this._openMenu.bind(this,h)}
onSelect={this._selectModule.bind(this, h)}
selected={currentMenu == h}
m={h.m}
/>);
}
}
return slots;
}
_getSectionMenu(translate) {
let _use = this._use;
return <div className='select' onClick={(e) => e.stopPropagation()}>
<ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
</ul>
<div className='select-group cap'>{translate('sb')}</div>
<ul>
<li className='c' onClick={_use.bind(this, 'sb', 'E')}>E</li>
<li className='c' onClick={_use.bind(this, 'sb', 'D')}>D</li>
<li className='c' onClick={_use.bind(this, 'sb', 'C')}>C</li>
<li className='c' onClick={_use.bind(this, 'sb', 'B')}>B</li>
<li className='c' onClick={_use.bind(this, 'sb', 'A')}>A</li>
</ul>
</div>;
}
}

72
src/app/i18n/Language.jsx Normal file
View File

@@ -0,0 +1,72 @@
import React from 'react';
import * as EN from './en';
import * as DE from './de';
import * as ES from './es';
import * as FR from './fr';
import * as IT from './it';
import * as RU from './ru';
import d3 from 'd3';
let fallbackTerms = EN.terms;
export function getLanguage(langCode) {
let lang, translate;
switch (langCode) {
case 'de': lang = DE; break;
case 'es': lang = ES; break;
case 'fr': lang = FR; break;
case 'it': lang = IT; break;
case 'ru': lang = RU; break;
default:
lang = EN;
}
let currentTerms = lang.terms;
let d3Locale = d3.locale(lang.formats);
let gen = d3Locale.numberFormat('n');
if(lang === EN) {
translate = (t) => { return currentTerms[t] || t; };
} else {
translate = (t) => { return currentTerms[t] || fallbackTerms[t] || t; };
}
return {
formats: {
gen: gen,
int: d3Locale.numberFormat(',.0f'),
pwr: d3Locale.numberFormat(',.2f'),
round: (d) => gen(d3.round(d, 2)),
pct: d3Locale.numberFormat('.2%'),
pct1: d3Locale.numberFormat('.1%'),
rPct: d3.format('%'),
time: (d) => Math.floor(d / 60) + ':' + ('00' + Math.floor(d % 60)).substr(-2, 2)
},
translate,
units: {
CR: <u>{' ' + translate('CR')}</u>, // Credits
kg: <u>{' ' + translate('kg')}</u>, // Kilograms
kgs: <u>{' ' + translate('kg/s')}</u>, // Kilograms per second
km: <u>{' ' + translate('km')}</u>, // Kilometers
Ls: <u>{' ' + translate('Ls')}</u>, // Light Seconds
LY: <u>{' ' + translate('LY')}</u>, // Light Years
MJ: <u>{' ' + translate('MJ')}</u>, // Mega Joules
ms: <u>{' ' + translate('m/s')}</u>, // Meters per second
MW: <u>{' ' + translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
ps: <u>{translate('/s')}</u>, // per second
T: <u>{' ' + translate('T')}</u>, // Metric Tons
}
}
}
export const Languages = {
en: 'English',
de: 'Deutsh',
it: 'Italiano',
es: 'Español',
fr: 'Français',
ru: 'ру́сский'
};

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