diff --git a/.docker/docker-compose.yml b/.docker/docker-compose.yml index 2c4bd98d..5a25f19f 100644 --- a/.docker/docker-compose.yml +++ b/.docker/docker-compose.yml @@ -7,10 +7,9 @@ services: volumes: - ./nginx.conf:/etc/nginx/nginx.conf networks: - - coriolis_web - - default + - web labels: - - "traefik.docker.network=coriolis_coriolis_web" + - "traefik.docker.network=web" - "traefik.enable=true" - "traefik.basic.frontend.rule=Host:coriolis.io,coriolis.edcd.io" - "traefik.basic.port=80" @@ -22,14 +21,14 @@ services: volumes: - ./nginx.conf:/etc/nginx/nginx.conf networks: - - coriolis_web - - default + - web labels: - - "traefik.docker.network=coriolis_coriolis_web" + - "traefik.docker.network=web" - "traefik.enable=true" - "traefik.basic.frontend.rule=Host:beta.coriolis.io,beta.coriolis.edcd.io" - "traefik.basic.port=80" - "traefik.basic.protocol=http" networks: - coriolis_web: + web: + external: true diff --git a/package-lock.json b/package-lock.json index 13b10ba3..43c25f9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1627,15 +1627,6 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, - "boom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", - "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", - "dev": true, - "requires": { - "hoek": "4.x.x" - } - }, "brace-expansion": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", @@ -3853,26 +3844,6 @@ "which": "^1.2.9" } }, - "cryptiles": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", - "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", - "dev": true, - "requires": { - "boom": "5.x.x" - }, - "dependencies": { - "boom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", - "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", - "dev": true, - "requires": { - "hoek": "4.x.x" - } - } - } - }, "crypto-browserify": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", @@ -5573,9 +5544,9 @@ "dev": true }, "fast-deep-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", "dev": true }, "fast-json-parse": { @@ -6940,9 +6911,9 @@ }, "dependencies": { "ajv": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.3.0.tgz", - "integrity": "sha1-RBT/dKUIecII7l/cgm4ywwNUnto=", + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "dev": true, "requires": { "co": "^4.6.0", @@ -6996,18 +6967,6 @@ "minimalistic-assert": "^1.0.0" } }, - "hawk": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", - "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", - "dev": true, - "requires": { - "boom": "4.x.x", - "cryptiles": "3.x.x", - "hoek": "4.x.x", - "sntp": "2.x.x" - } - }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", @@ -7025,12 +6984,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "hoek": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", - "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==", - "dev": true - }, "home-or-tmp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", @@ -9512,9 +9465,9 @@ } }, "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" }, "lodash.assignin": { "version": "4.2.0", @@ -11851,9 +11804,9 @@ } }, "request": { - "version": "2.83.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", - "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", + "version": "2.87.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", + "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", "dev": true, "requires": { "aws-sign2": "~0.7.0", @@ -11864,7 +11817,6 @@ "forever-agent": "~0.6.1", "form-data": "~2.3.1", "har-validator": "~5.0.3", - "hawk": "~6.0.2", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", @@ -11874,7 +11826,6 @@ "performance-now": "^2.1.0", "qs": "~6.5.1", "safe-buffer": "^5.1.1", - "stringstream": "~0.0.5", "tough-cookie": "~2.3.3", "tunnel-agent": "^0.6.0", "uuid": "^3.1.0" @@ -12261,15 +12212,6 @@ "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", "dev": true }, - "sntp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", - "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", - "dev": true, - "requires": { - "hoek": "4.x.x" - } - }, "sockjs": { "version": "0.3.18", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.18.tgz", @@ -12546,7 +12488,8 @@ "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", - "dev": true + "dev": true, + "optional": true }, "strip-ansi": { "version": "3.0.1", diff --git a/package.json b/package.json index 21975896..9c30e406 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "d3": "4.8.0", "detect-browser": "^1.7.0", "fbemitter": "^2.1.1", - "lodash": "^4.17.4", + "lodash": "^4.17.10", "lz-string": "^1.4.4", "pako": "^1.0.6", "prop-types": "^15.5.8", diff --git a/src/app/components/Header.jsx b/src/app/components/Header.jsx index 602b6c10..24b67b0c 100644 --- a/src/app/components/Header.jsx +++ b/src/app/components/Header.jsx @@ -9,6 +9,8 @@ import { Cogs, CoriolisLogo, Hammer, Help, Rocket, StatsBars } from './SvgIcons' import { Ships } from 'coriolis-data/dist'; import Persist from '../stores/Persist'; import { toDetailedExport } from '../shipyard/Serializer'; +import Ship from '../shipyard/Ship'; +import ModalBatchOrbis from './ModalBatchOrbis'; import ModalDeleteAll from './ModalDeleteAll'; import ModalExport from './ModalExport'; import ModalHelp from './ModalHelp'; @@ -235,6 +237,39 @@ export default class Header extends TranslatedComponent { />); }; + _uploadAllBuildsToOrbis(e) { + e.preventDefault(); + const data = Persist.getBuilds(); + let postObject = []; + for (const ship in data) { + for (const code in data[ship]) { + const shipModel = ship; + if (!shipModel) { + throw 'No such ship found: "' + ship + '"'; + } + const shipTemplate = Ships[shipModel]; + const shipPostObject = {}; + let shipInstance = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots); + shipInstance.buildWith(null); + shipInstance.buildFrom(data[ship][code]); + shipPostObject.coriolisId = shipInstance.id; + shipPostObject.coriolisShip = shipInstance; + + shipPostObject.coriolisShip.url = window.location.origin + outfitURL(shipModel, data[ship][code], code); + shipPostObject.title = code || shipInstance.id; + shipPostObject.description = code || shipInstance.id; + shipPostObject.ShipName = shipInstance.id; + shipPostObject.Ship = shipInstance.id; + postObject.push(shipPostObject); + } + } + console.log(postObject); + + this.context.showModal(); + } + /** * Show export modal with detailed export * @param {SyntheticEvent} e Event @@ -430,6 +465,7 @@ export default class Header extends TranslatedComponent { {translate('builds')} & {translate('comparisons')}
  • {translate('backup')}
  • {translate('detailed export')}
  • +
  • {translate('upload all builds to orbis')}
  • {translate('import')}
  • {translate('delete all')}
  • diff --git a/src/app/components/ModalBatchOrbis.jsx b/src/app/components/ModalBatchOrbis.jsx new file mode 100644 index 00000000..11a19e97 --- /dev/null +++ b/src/app/components/ModalBatchOrbis.jsx @@ -0,0 +1,92 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import request from 'superagent'; +import TranslatedComponent from './TranslatedComponent'; +import { orbisUpload } from '../utils/ShortenUrl'; +import Persist from '../stores/Persist'; + +/** + * Permalink modal + */ +export default class ModalBatchOrbis extends TranslatedComponent { + + static propTypes = { + ships: PropTypes.any.isRequired + }; + + /** + * Constructor + * @param {Object} props React Component properties + */ + constructor(props) { + super(props); + + this.state = { + orbisCreds: Persist.getOrbisCreds(), + resp: '' + }; + } + + /** + * Send ship to Orbis.zone + * @param {SyntheticEvent} e React Event + */ + sendToOrbis(e) { + let agent; + try { + agent = request.agent(); // apparently this crashes somehow + } catch (e) { + console.error(e); + } + if (!agent) { + agent = request; + } + const API_ORBIS = 'https://orbis.zone/api/builds/add/batch'; + return new Promise((resolve, reject) => { + try { + agent + .post(API_ORBIS) + .withCredentials() + .redirects(0) + .set('Content-Type', 'application/json') + .send(this.props.ships) + .end((err, response) => { + console.log(response); + if (err) { + console.error(err); + this.setState({ resp: response.text }); + reject('Bad Request'); + } else { + this.setState({ resp: 'All builds uploaded. Check https://orbis.zone' }); + resolve('All builds uploaded. Check https://orbis.zone'); + } + }); + } catch (e) { + console.log(e); + reject(e.message ? e.message : e); + } + }); + } + + /** + * Render the modal + * @return {React.Component} Modal Content + */ + render() { + let translate = this.context.language.translate; + this.sendToOrbis = this.sendToOrbis.bind(this); + + return
    e.stopPropagation() }> +

    {translate('permalink')}

    +
    + Log in / signup to Orbis +

    +

    {translate('success')}

    + e.target.select() }/> +

    +

    Orbis.zone is currently in a trial period, and may be wiped at any time as development progresses. Some elements are also still placeholders.

    + + +
    ; + } +} diff --git a/src/app/components/ModalOrbis.jsx b/src/app/components/ModalOrbis.jsx new file mode 100644 index 00000000..56896fd4 --- /dev/null +++ b/src/app/components/ModalOrbis.jsx @@ -0,0 +1,117 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import TranslatedComponent from './TranslatedComponent'; +import { orbisUpload } from '../utils/ShortenUrl'; +import Persist from '../stores/Persist'; + +/** + * Permalink modal + */ +export default class ModalOrbis extends TranslatedComponent { + + static propTypes = { + ship: PropTypes.any.isRequired + }; + + /** + * Constructor + * @param {Object} props React Component properties + */ + constructor(props) { + super(props); + + this.state = { + orbisCreds: Persist.getOrbisCreds(), + orbisUrl: '...', + authenticatedStatus: 'Checking...' + }; + } + + /** + * Send ship to Orbis.zone + * @param {SyntheticEvent} e React Event + */ + sendToOrbis(e) { + const target = e.target; + target.disabled = true; + this.setState({ orbisUrl: 'Sending...' }, () => { + orbisUpload(this.props.ship, this.state.orbisCreds) + .then(orbisUrl => { + target.disabled = false; + this.setState({ orbisUrl }); + }) + .catch(err => { + target.disabled = false; + this.setState({ orbisUrl: 'Error - ' + err }); + }); + }); + } + + /** + * Get Orbis.zone auth status + * @returns {Object} auth status + */ + getOrbisAuthStatus() { + return fetch('https://orbis.zone/api/checkauth', { + credentials: 'include', + mode: 'cors' + }) + .then(data => data.json()) + .then(res => { + this.setState({ authenticatedStatus: res.status || res.error }); + }) + .catch(err => { + console.error(err); + this.setState({ authenticatedStatus: err.message }); + }); + } + + /** + * Handler for changing cmdr name + * @param {SyntheticEvent} e React Event + */ + orbisPasswordHandler(e) { + let password = e.target.value; + this.setState({ orbisCreds: { email: this.state.orbisCreds.email, password } }, () => { + Persist.setOrbisCreds(this.state.orbisCreds); + }); + } + + /** + * Handler for changing cmdr name + * @param {SyntheticEvent} e React Event + */ + orbisUsername(e) { + let orbisUsername = e.target.value; + this.setState({ orbisCreds: { email: orbisUsername, password: this.state.orbisCreds.password } }, () => { + Persist.setOrbisCreds(this.state.orbisCreds); + }); + } + + /** + * Render the modal + * @return {React.Component} Modal Content + */ + render() { + let translate = this.context.language.translate; + this.orbisPasswordHandler = this.orbisPasswordHandler.bind(this); + this.orbisUsername = this.orbisUsername.bind(this); + this.sendToOrbis = this.sendToOrbis.bind(this); + this.getOrbisAuthStatus(); + return
    e.stopPropagation() }> +

    {translate('upload to orbis')}

    +
    + + e.target.select() }/> +

    + Log in / signup to Orbis +

    +

    {translate('Orbis link')}

    + e.target.select() }/> +

    +

    Orbis.zone is currently in a trial period, and may be wiped at any time as development progresses. Some elements are also still placeholders.

    + + +
    ; + } +} diff --git a/src/app/components/ModalShoppingList.jsx b/src/app/components/ModalShoppingList.jsx index 047e69bb..7c92a919 100644 --- a/src/app/components/ModalShoppingList.jsx +++ b/src/app/components/ModalShoppingList.jsx @@ -208,7 +208,7 @@ export default class ModalShoppingList extends TranslatedComponent { } /** - * Handler for changing roll amounts + * Handler for changing cmdr name * @param {SyntheticEvent} e React Event */ cmdrChangeHandler(e) { diff --git a/src/app/components/SvgIcons.jsx b/src/app/components/SvgIcons.jsx index 2933ac32..c226f892 100644 --- a/src/app/components/SvgIcons.jsx +++ b/src/app/components/SvgIcons.jsx @@ -228,6 +228,30 @@ export class LinkIcon extends SvgIcon { } } +/** + * Link / Permalink / Chain + */ +export class OrbisIcon extends SvgIcon { + /** + * Generate the SVG + * @return {React.Component} SVG Contents + */ + svg() { + return ( + + + + + + + + + + ); + } +} + + /** * Material */ diff --git a/src/app/i18n/en.json b/src/app/i18n/en.json index 4836723f..99624a88 100644 --- a/src/app/i18n/en.json +++ b/src/app/i18n/en.json @@ -1,340 +1,343 @@ -{ - "PHRASE_ALT_ALL": "Alt + Click to fill all slots", - "PHRASE_BACKUP_DESC": "Backup of all Coriolis data to save or transfer to another browser/device", - "PHRASE_CONFIRMATION": "Are you sure?", - "PHRASE_EXPORT_DESC": "A detailed JSON export of your build for use in other sites and tools", - "PHRASE_FASTEST_RANGE": "Consecutive max range jumps", - "PHRASE_IMPORT": "Paste JSON or import here", - "PHRASE_LADEN": "Ship mass + fuel + cargo", - "PHRASE_NO_BUILDS": "No builds added to comparison!", - "PHRASE_NO_RETROCH": "No Retrofitting changes", - "PHRASE_SELECT_BUILDS": "Select builds to compare", - "PHRASE_SG_RECHARGE": "Time from 50% to 100% charge, assuming full SYS capacitor to start with", - "PHRASE_SG_RECOVER": "Time from 0% to 50% charge, assuming full SYS capacitor to start with", - "PHRASE_UNLADEN": "Ship mass excluding fuel and cargo", - "PHRASE_UPDATE_RDY": "Update Available! Click to refresh", - "PHRASE_ENGAGEMENT_RANGE": "The distance between your ship and its target", - "PHRASE_SELECT_BLUEPRINT": "Click to select a blueprint", - "PHRASE_BLUEPRINT_WORST": "Worst primary values for this blueprint", - "PHRASE_BLUEPRINT_FIFTY": "50% of full values for this blueprint", - "PHRASE_BLUEPRINT_SEVEN_FIVE": "75% of full values for this blueprint", - "PHRASE_BLUEPRINT_RANDOM": "Random selection between worst and best primary values for this blueprint", - "PHRASE_BLUEPRINT_BEST": "Best primary values for this blueprint", - "PHRASE_BLUEPRINT_EXTREME": "Best beneficial and worst detrimental primary values for this blueprint", - "PHRASE_BLUEPRINT_RESET": "Remove all modifications and blueprint", - "PHRASE_SELECT_SPECIAL": "Click to select an experimental effect", - "PHRASE_NO_SPECIAL": "No experimental effect", - "PHRASE_SHOPPING_LIST": "Stations that sell this build", - "PHRASE_SHOPPING_MATS": "Materials needed for this build", - "PHRASE_REFIT_SHOPPING_LIST": "Stations that sell required modules", - "PHRASE_TOTAL_EFFECTIVE_SHIELD": "Total amount of damage that can be taken from each damage type, if using all shield cells", - "PHRASE_TIME_TO_LOSE_SHIELDS": "Shields will hold for", - "PHRASE_TIME_TO_RECOVER_SHIELDS": "Shields will recover in", - "PHRASE_TIME_TO_RECHARGE_SHIELDS": "Shields will recharge in", - "PHRASE_SHIELD_SOURCES": "Breakdown of the supply of shield energy", - "PHRASE_EFFECTIVE_SHIELD": "Effective shield strength against different damage types", - "PHRASE_ARMOUR_SOURCES": "Breakdown of the supply of armour", - "PHRASE_EFFECTIVE_ARMOUR": "Effective armour strength against different damage types", - "PHRASE_DAMAGE_TAKEN": "% of raw damage taken for different damage types", - "PHRASE_TIME_TO_LOSE_ARMOUR": "Armour will hold for", - "PHRASE_MODULE_PROTECTION_EXTERNAL": "Protection for hardpoints", - "PHRASE_MODULE_PROTECTION_INTERNAL": "Protection for all other modules", - "PHRASE_SHIELD_DAMAGE": "Breakdown of sources for sustained DPS against shields", - "PHRASE_ARMOUR_DAMAGE": "Breakdown of sources for sustained DPS against armour", - "PHRASE_TIME_TO_REMOVE_SHIELDS": "Will remove shields in", - "TT_TIME_TO_REMOVE_SHIELDS": "With sustained fire by all weapons", - "PHRASE_TIME_TO_REMOVE_ARMOUR": "Will remove armour in", - "TT_TIME_TO_REMOVE_ARMOUR": "With sustained fire by all weapons", - "PHRASE_TIME_TO_DRAIN_WEP": "Will drain WEP in", - "TT_TIME_TO_DRAIN_WEP": "Time to drain WEP capacitor with all weapons firing", - "TT_TIME_TO_LOSE_SHIELDS": "Against sustained fire from all opponent's weapons", - "TT_TIME_TO_LOSE_ARMOUR": "Against sustained fire from all opponent's weapons", - "TT_MODULE_ARMOUR": "Armour protecting against module damage", - "TT_MODULE_PROTECTION_EXTERNAL": "Percentage of damage diverted from hardpoints to module reinforcement packages", - "TT_MODULE_PROTECTION_INTERNAL": "Percentage of damage diverted from non-hardpoint modules to module reinforcement packages", - "TT_EFFECTIVE_SDPS_SHIELDS": "Actual sustained DPS whilst WEP capacitor is not empty", - "TT_EFFECTIVENESS_SHIELDS": "Effectivness compared to hitting a 0-resistance target with 0 pips to SYS at 0m", - "TT_EFFECTIVE_SDPS_ARMOUR": "Actual sustained DPS whilst WEP capacitor is not empty", - "TT_EFFECTIVENESS_ARMOUR": "Effectivness compared to hitting a 0-resistance target at 0m", - "PHRASE_EFFECTIVE_SDPS_SHIELDS": "SDPS against shields", - "PHRASE_EFFECTIVE_SDPS_ARMOUR": "SDPS against armour", - "TT_SUMMARY_SPEED": "With full fuel tank and 4 pips to ENG", - "TT_SUMMARY_SPEED_NONFUNCTIONAL": "Thrusters powered off or over maximum mass with full fuel and cargo loads", - "TT_SUMMARY_BOOST": "With full fuel tank and 4 pips to ENG", - "TT_SUMMARY_BOOST_TIME": "Time between each boost with 4 pips to ENG", - "TT_SUMMARY_BOOST_NONFUNCTIONAL": "Power distributor not able to supply enough power to boost", - "TT_SUMMARY_SHIELDS": "Raw shield strength, including boosters", - "TT_SUMMARY_SHIELDS_SCB": "Raw shield strength, including boosters and SCBs", - "TT_SUMMARY_SHIELDS_NONFUNCTIONAL": "No shield generator or shield generator powered off", - "TT_SUMMARY_INTEGRITY": "Ship integrity, including bulkheads and hull reinforcement packages", - "TT_SUMMARY_HULL_MASS": "Mass of the hull prior to any modules being installed", - "TT_SUMMARY_UNLADEN_MASS": "Mass of the hull and modules prior to any fuel or cargo", - "TT_SUMMARY_LADEN_MASS": "Mass of the hull and modules with full fuel and cargo", - "TT_SUMMARY_DPS": "Damage per second with all weapons firing", - "TT_SUMMARY_EPS": "WEP capacitor consumed per second with all weapons firing", - "TT_SUMMARY_TTD": "Time to drain WEP capacitor with all weapons firing and 4 pips to WEP", - "TT_SUMMARY_MAX_SINGLE_JUMP": "Farthest possible jump range with no cargo and only enough fuel for the jump itself", - "TT_SUMMARY_UNLADEN_SINGLE_JUMP": "Farthest possible jump range with no cargo and a full fuel tank", - "TT_SUMMARY_LADEN_SINGLE_JUMP": "Farthest possible jump range with full cargo and a full fuel tank", - "TT_SUMMARY_UNLADEN_TOTAL_JUMP": "Farthest possible range with no cargo, a full fuel tank, and jumping as far as possible each time", - "TT_SUMMARY_LADEN_TOTAL_JUMP": "Farthest possible range with full cargo, a full fuel tank, and jumping as far as possible each time", - "HELP_MODIFICATIONS_MENU": "Click on a number to enter a new value, or drag along the bar for small changes", - "am": "Auto Field-Maintenance Unit", - "bh": "Bulkheads", - "bl": "Beam Laser", - "bsg": "Bi-Weave Shield Generator", - "c": "Cannon", - "cc": "Collector Limpet Controller", - "ch": "Chaff Launcher", - "cr": "Cargo Rack", - "cs": "Manifest Scanner", - "dc": "Docking Computer", - "ec": "Electronic Countermeasure", - "fc": "Fragment Cannon", - "fh": "Fighter Hangar", - "fi": "FSD Interdictor", - "fs": "Fuel Scoop", - "fsd": "Frame Shift Drive", - "ft": "Fuel Tank", - "fx": "Fuel Transfer Limpet Controller", - "hb": "Hatch Breaker Limpet Controller", - "hr": "Hull Reinforcement Package", - "hs": "Heat Sink Launcher", - "kw": "Kill Warrant Scanner", - "ls": "Life Support", - "mc": "Multi-cannon", - "axmc": "AX Multi-cannon", - "ml": "Mining Laser", - "mr": "Missile Rack", - "axmr": "AX Missile Rack", - "mrp": "Module Reinforcement Package", - "nl": "Mine Launcher", - "pa": "Plasma Accelerator", - "pas": "Planetary Approach Suite", - "pc": "Prospector Limpet Controller", - "pce": "Economy Class Passenger Cabin", - "pci": "Business Class Passenger Cabin", - "pcm": "First Class Passenger Cabin", - "pcq": "Luxury Passenger Cabin", - "pd": "power distributor", - "pl": "Pulse Laser", - "po": "Point Defence", - "pp": "Power Plant", - "gpp": "Guardian Hybrid Power Plant", - "gpd": "Guardian Hybrid Power Distributor", - "gpc": "Guardian Plasma Charger", - "ggc": "Guardian Gauss Cannon", - "gsrp": "Guardian Shield Reinforcement Package", - "gfsb": "Guardian Frame Shift Drive Booster", - "ghrp": "Guardian Hull Reinforcement Package", - "gmrp": "Guardian Module Reinforcement Package", - "tbsc": "Shock Cannon", - "gsc": "Guardian Shard Cannon", - "psg": "Prismatic Shield Generator", - "pv": "Planetary Vehicle Hangar", - "rf": "Refinery", - "rfl": "Remote Release Flak Launcher", - "rg": "Rail Gun", - "s": "Sensors", - "sb": "Shield Booster", - "sc": "Stellar Scanners", - "scb": "Shield Cell Bank", - "sfn": "Shutdown Field Neutraliser", - "sg": "Shield Generator", - "ss": "Surface Scanners", - "t": "thrusters", - "tp": "Torpedo Pylon", - "ul": "Burst Laser", - "Send To EDEngineer": "Send To EDEngineer", - "ws": "Frame Shift Wake Scanner", - "rpl": "Repair Limpet Controller", - "rcpl": "Recon Limpet Controller", - "xs": "Xeno Scanner", - "emptyrestricted": "empty (restricted)", - "damage dealt to": "Damage dealt to", - "damage received from": "Damage received from", - "against shields": "Against shields", - "against hull": "Against hull", - "total effective shield": "Total effective shield", - "ammunition": "Ammo", - "secs": "s", - "rebuildsperbay": "Rebuilds per bay", - "worst": "Worst", - "average": "Average", - "random": "Random", - "best": "Best", - "extreme": "Extreme", - "reset": "Reset", - "dpe": "Damage per MJ of energy", - "dps": "Damage per second", - "sdps": "Sustained damage per second", - "dpssdps": "Damage per second (sustained damage per second)", - "eps": "Energy per second", - "epsseps": "Energy per second (sustained energy per second)", - "hps": "Heat per second", - "hpsshps": "Heat per second (sustained heat per second)", - "damage by": "Damage by", - "damage from": "Damage from", - "shield cells": "Shield cells", - "recovery": "Recovery", - "recharge": "Recharge", - "engine pips": "Engine Pips", - "4b": "4 pips and boost", - "speed": "Speed", - "pitch": "Pitch", - "roll": "Roll", - "yaw": "Yaw", - "internal protection": "Internal protection", - "external protection": "External protection", - "engagement range": "Engagement range", - "boost time": "Boost time", - "total": "Total", - "ammo": "Ammunition maximum", - "boot": "Boot time", - "hacktime": "Hack time", - "brokenregen": "Broken regeneration rate", - "burst": "Burst", - "burstrof": "Burst rate of fire", - "clip": "Ammunition clip", - "damage": "Damage", - "distdraw": "Distributor draw", - "duration": "Duration", - "eff": "Efficiency", - "engcap": "Engines capacity", - "engrate": "Engines recharge rate", - "explres": "Explosive resistance", - "facinglimit": "Facing limit", - "hullboost": "Hull boost", - "hullreinforcement": "Hull reinforcement", - "integrity": "Integrity", - "jitter": "Jitter", - "kinres": "Kinetic resistance", - "maxfuel": "Maximum fuel per jump", - "mass": "Mass", - "optmass": "Optimal mass", - "optmul": "Optimal multiplier", - "pgen": "Power generation", - "piercing": "Piercing", - "power": "Power draw", - "protection": "Protection", - "range": "Range", - "ranget": "Range", - "regen": "Regeneration rate", - "reload": "Reload", - "rof": "Rate of fire", - "angle": "Scan angle", - "scanrate": "Scan rate", - "scantime": "Scan time", - "shield": "Shield", - "armour": "Armour", - "shieldboost": "Shield boost", - "shieldreinforcement": "Shield reinforcement", - "shotspeed": "Shot speed", - "spinup": "Spin up time", - "syscap": "Systems capacity", - "sysrate": "Systems recharge rate", - "thermload": "Thermal load", - "thermres": "Thermal resistance", - "wepcap": "Weapons capacity", - "weprate": "Weapons recharge rate", - "minmass_sg": "Minimum hull mass", - "optmass_sg": "Optimal hull mass", - "maxmass_sg": "Maximum hull mass", - "minmul_sg": "Minimum strength", - "optmul_sg": "Optimal strength", - "maxmul_sg": "Minimum strength", - "minmass_psg": "Minimum hull mass", - "optmass_psg": "Optimal hull mass", - "maxmass_psg": "Maximum hull mass", - "minmul_psg": "Minimum strength", - "optmul_psg": "Optimal strength", - "maxmul_psg": "Minimum strength", - "minmass_bsg": "Minimum hull mass", - "optmass_bsg": "Optimal hull mass", - "maxmass_bsg": "Maximum hull mass", - "minmul_bsg": "Minimum strength", - "optmul_bsg": "Optimal strength", - "maxmul_bsg": "Minimum strength", - "range_s": "Typical emission range", - "absolute": "Absolute", - "explosive": "Explosive", - "kinetic": "Kinetic", - "thermal": "Thermal", - "generator": "Generator", - "boosters": "Boosters", - "cells": "Cells", - "shield addition": "Shield Addition", - "jump addition": "Jump Addition", - "bulkheads": "Bulkheads", - "reinforcement": "Reinforcement", - "power and costs": "power and costs", - "costs": "costs", - "retrofit costs": "retrofit costs", - "reload costs": "reload costs", - "profiles": "profiles", - "engine profile": "engine profile", - "fsd profile": "fsd profile", - "movement profile": "movement profile", - "damage to opponent's shields": "damage to opponent's shields", - "damage to opponent's hull": "damage to opponent's hull", - "offence": "offence", - "defence": "defence", - "shield metrics": "shield metrics", - "raw shield strength": "raw shield strength", - "shield sources": "shield sources", - "damage taken": "damage taken", - "effective shield": "effective shield", - "armour metrics": "armour metrics", - "raw armour strength": "raw armour strength", - "armour sources": "armour sources", - "raw module armour": "raw module armour", - "effective armour": "effective armour", - "offence metrics": "offence metrics", - "defence metrics": "defence metrics", - "fuel carried": "fuel carried", - "cargo carried": "cargo carried", - "ship control": "ship control", - "opponent": "opponent", - "opponent's shields": "opponent's shields", - "opponent's armour": "opponent's armour", - "shield damage sources": "shield damage sources", - "armour damage sources": "armour damage sources", - "never": "never", - "stock": "stock", - "boost": "boost", - "federation rank 1": "Recruit", - "federation rank 2": "Cadet", - "federation rank 3": "Midshipman", - "federation rank 4": "Petty Officer", - "federation rank 5": "Chief Petty Officer", - "federation rank 6": "Warrant Officer", - "federation rank 7": "Ensign", - "federation rank 8": "Lieutenant", - "federation rank 9": "Lieutenant Commander", - "federation rank 10": "Post Commander", - "federation rank 11": "Post Captain", - "federation rank 12": "Rear Admiral", - "federation rank 13": "Vice Admiral", - "federation rank 14": "Admiral", - "federation rank required": "Minimum Federation rank required to purchase", - "empire rank 1": "Outsider", - "empire rank 2": "Serf", - "empire rank 3": "Master", - "empire rank 4": "Squire", - "empire rank 5": "Knight", - "empire rank 6": "Lord", - "empire rank 7": "Baron", - "empire rank 8": "Viscount", - "empire rank 9": "Count", - "empire rank 10": "Earl", - "empire rank 11": "Marquis", - "empire rank 12": "Duke", - "empire rank 13": "Prince", - "empire rank 14": "King", - "empire rank required": "Minimum Empire rank required to purchase", - "horizons": "Horizons", - "horizons required": "Only available to those who have purchased Elite Dangerous: Horizons", - "horizons early adoption": "Horizons Early Adopter", - "horizons early adoption required": "Only available to those who purchased Elite Dangerous: Horizons on PC before February 5, 2016 or on Xbox One before July 30, 2016", - "HELP_TEXT": "\n

    Introduction

    \nCoriolis is a ship builder for Elite: Dangerous. This help file provides you with the information you need to use Coriolis.\n\n

    Importing Your Ship Into Coriolis

    \nOften, you will want to start with your existing ship in Coriolis and see how particular changes might affect it, for example upgrading your FSD. There are a number of tools that can be used to import your ship without you having to create it manually. This has the added benefit of copying over any engineering modifications that have taken place as well.

    \n\n

    Importing Your Ship From EDDI

    \nTo import your ship from EDDI first ensure that your connection to the Frontier servers' companion API is working. To do this check the 'Companion App' tab where you should see \"Your connection to the companion app is operational\". If not then follow the instructions in the companion app tab in EDDI to connect to the Frontier servers.

    \n\nOnce you have a working companion API connection go to the 'Shipyard' tab. At the right-hand side of each ship is an 'Export to Coriolis' button that will open your default web browser in Coriolis with the ship's build.

    \n\nNote that Internet Explorer and Edge might not import correctly, due to their internal restrictions on URL length. If you find that this is the case then please change your default browser to Chrome.

    \n\nAlso, the imported information does not provide any data on the power priority or enabled status of your cargo hatch. Coriolis sets this item to have a power priority of \"5\" and to be disabled by default. You can change this after import in the Power Management section.

    \n\n

    Importing Your Ship From EDMC

    \nTo import your ship from EDMC once your connection to the Frontier servers' companion API is working go to 'Settings ->Configuration' and set the 'Preferred Shipyard' to 'Coriolis'. Once this is set up clicking on your ship in the main window will open your default web browser in Coriolis with the ship's build.

    \n\nNote that Internet Explorer and Edge might not import correctly, due to their internal restrictions on URL length. If you find that this is the case then please change your default browser to Chrome.

    \n\n

    Understanding And Using The Outfitting Panels

    \nThe outfitting page is where you will spend most of your time, and contains the information for your ship. Information on each of the panels is provided below.

    \n\n

    Key Values

    \nAlong the top of the screen are some of the key values for your build. This is a handy reference for the values, but more information is provided for the values in the further panels.

    \n\nHere, along with most places in Coriolis, acronyms will have tooltips explaining what they mean. Hover over the acronym to obtain more detail, or look in the glossary at the end of this help.

    \n\nAll values are the highest possible, assuming that you an optimal setup for that particular value (maximum pips in ENG for speed, minimum fuel for jump range, etc.). This means that these values will not be affected by changes to pip settings. Details of the specific setup for each value are listed in the associated tootip.

    \n\n

    Modules

    \nThe next set of panels laid out horizontally across the screen contain the modules you have put in your build. From left to right these are the core modules, the internal modules, the hardpoints and the utility mounts. These represent the available slots in your ship and cannot be altered. Each slot has a class, or size, and in general any module up to a given size can fit in a given slot (exceptions being bulkheads, life support and sensors in core modules and restricted internal slots, which can only take a subset of module depending on their restrictions).

    \n\nTo add a module to a slot left-click on the slot and select the required module. Only the modules capable of fitting in the selected slot will be shown.

    \n\nTo remove a module from a slot right-click on the module.

    \n\nTo move a module from one slot to another drag it. If you instead want to copy the module drag it whilst holding down the 'Alt' key.

    \n\nClicking on the headings for each set of modules gives you the ability to either select an overall role for your ship (when clicking the core internal header) or a specific module with which you want to fill all applicable slots (when clicking the other headers).

    \n\n

    Ship Controls

    \nThe ship controls allow you to set your pips, boost, and amount of fuel and cargo that your build carries. The changes made here will effect the information supplied in the subsequent panels, giving you a clearer view of what effect different changing these items will have.

    \n\nShip control settings are saved as part of a build.

    \n\n

    Opponent

    \nThe opponet selection allows you to choose your opponent. The opponent can be either a stock build of a ship or one of your own saved builds. You can also set the engagement range between you and your opponent. Your selection here will effect the information supplied in the subsequent panels, specifically the Offence and Defence panels.

    \n\nOpponent settings are saved as part of a build.

    \n\n

    Power and Costs Sub-panels

    \n

    Power

    \nThe power management panel provides information about power usage and priorities. It allows you to enable and disable individual modules, as well as set power priorities for each module. Disabled modules will not be included in the build's statistics, with the exception of Shield Cell Banks as they are usually disabled when not in use and only enabled when required.

    \n\n

    Costs

    \nThe costs panel provides information about the costs for each of your modules, and the total cost and insurance for your build. By default Coriolis uses the standard costs, however discounts for your ship, modules and insurance can be altered in the 'Settings' at the top-right of the page.

    \n\nThe retrofit costs provides information about the costs of changing the base build for your ship, or your saved build, to the current build.

    \n\nThe reload costs provides information about the costs of reloading your current build.

    \n\n

    Profiles

    \nProfiles provide graphs that show the general performance of modules in your build\n\n

    Engine Profile

    \nThe engine profile panel provides information about the capabilities of your current thrusters. The graph shows you how the maximum speed alters with the overall mass of your build. The vertical dashed line on the graph shows your current mass. Your engine profile can be altered by obtaining different thrusters or engineering your existing thrusters, and you can increase your maximum speed by adding pips to the ENG capacitor as well as reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build. You can also temporarily increase your speed by hitting the boost button.

    \n\n

    FSD Profile

    \nThe FSD profile panel provides information about the capabilities of your current frame shift drive. The graph shows you how the maximum jump range alters with the overall mass of your build. The vertical dashed line on the graph shows your current maximum single jump range. Your FSD profile can be altered by obtaining a different FSD or engineering your existing FSD, and you can increase your maximum jump range by reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build,

    \n\n

    Movement Profile

    \nThe movement profile panel provides information about the capabilities of your current thrusters with your current overall mass and ENG pips settings. The diagram shows your ability to move and rotate in the different axes:\n\n
    \n
    Speed
    The fastest the ship can move, in metres per second
    \n
    Pitch
    The fastest the ship can raise or lower its nose, in degrees per second
    \n
    Roll
    The fastest the ship can roll its body, in degrees per second
    \n
    Yaw
    The fastest the ship can turn its nose left or right, in degrees per second
    \n
    \n\nYour movement profile can be altered by obtaining different thrusters or engineering your existing thrusters, and you can increase your movement values by adding pips to the ENG capacitor as well as reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build. You can also temporarily increase your movement profile by hitting the boost button.

    \n\n

    Damage Profile

    \nThe damage profile provides two graphs showing how the the build's damage to the opponent's shields and hull change with engagement range. The vertical dashed line on the graph shows your current engagement range. This combines information about the build's weapons with the opponent's shields and hull to provide an accurate picture of sustained damage that can be inflicted on the opponent.

    \n\n

    Offence

    \n

    Summary

    \nThe offence summary provides per-weapon information about sustained damage per second inflicted to shields and hull, along with a measure of effectiveness of that weapon. The effectiveness value has a tooltip that provides a breakdown of the effectiveness, and can include reductions or increases due to range, resistance, and either power distributor (for shields) or hardness (for hull). The final effectiveness value is calculated by multiplying these percentages together.

    \n\n

    Offence Metrics

    \nThe offence metrics panel provides information about your offence.

    \n\nTime to drain is a measure of how quickly your WEP capacitor will drain when firing all weapons. It is affected by the number of pips you have in your WEP capacitor, with more pips resulting in a higher WEP recharge rate and hence a longer time to drain.

    \n\nThe next value is the time it will take you to remove your opponent's shields. This assumes that you have 100% time on target and that your engagement range stays constant. Note that if your time to remove shields is longer than your time to drain this assumes that you continue firing throughout, inflicting lower damage due to the reduced energy in your WEP capacitor.

    \n\nThe next value is the time it will take you to remove your opponent's armour. This follows the same logic as the time to remove shields.

    \n\n

    Shield Damage Sources

    \nThe shield damage sources provides information about the sources of damage to your opponent by damage type. For each applicable type of damage (absolute explosive, kinetic, thermal) a sustained damage per second value is provided.

    \n\n

    Hull Damage Sources

    \nThe hull damage sources provides information about the sources of damage to your opponent by damage type. For each applicable type of damage (absolute explosive, kinetic, thermal) a sustained damage per second value is provided.

    \n\n

    Defence

    \n

    Shield Metrics

    \nThe shield metrics provides information about your shield defence.

    \n\nRaw shield strength is the sum of the shield from your generator, boosters and shield cell banks. A tooltip provides a breakdown of these values.

    \n\nThe time the shields will hold for is the time it will take your opponent' to remove your shields. This assumes that they have 100% time on target and that the engagement range stays constant. It also assumes that you fire all of your shield cell banks prior to your shields being lost.

    \n\nThe time the shields will recover in is the time it will take your shields to go from collapsed (0%) to recovered (50%). This is affected by the number of pips you have in your SYS capacitor.

    \n\nThe time the shields will recharge in is the time it will take your shields to go from recovered (50%) to full (100%). This is affected by the number of pips you have in your SYS capacitor.

    \n\nShield Sources\nThis chart provides information about the sources of your shields. For each applicable source of shields (generator, boosters, shield cell banks) a value is provided.

    \n\nDamage Taken\nThis graph shows how the initial damage from the weapons of each type are reduced before their damage is applied to the shields. For each type of damage (absolute, explosive, kinetic, thermal) a percentage of the initial damage is provided. A tooltip provides a breakdown of these values.

    \n\nEffective Shield\nThis graph shows the effective shield for each damage type, found by dividing the raw shield value by the damage taken for that type.

    \n\n

    Amour Metrics

    \nThe armour metrics provides information about your armour defence.

    \n\nRaw armour strength is the sum of the armour from your bulkheads and hull reinforcement packages. A tooltip provides a breakdown of these values.

    \n\nThe time the armour will hold for is the time it will take your opponent' to take your armour to 0. This assumes that they have 100% time on target, the engagement range stays constant, and that all damage is dealt to the armour rather than modules.

    \n\nRaw module armour is the sum of the protection from your module reinforcement packages.

    \n\nProtection for hardpoints is the amount of protection that your module reinforcement packages provide to hardpoints. This percentage of damage to the hardpoints will be diverted to the module reinforcement packages.

    \n\nProtection for all other modules is the amount of protection that your module reinforcement packages provide to everything other than hardpoints. This percentage of damage to the modules will be diverted to the module reinforcement packages.

    \n\nArmour Sources\nThis chart provides information about the sources of your armour. For each applicable source of shields (bulkheads, hull reinforcement packages) a value is provided.

    \n\nDamage Taken\nThis graph shows how the initial damage from the weapons of each type are reduced before their damage is applied to the armour. For each type of damage (absolute, explosive, kinetic, thermal) a percentage of the initial damage is provided. A tooltip provides a breakdown of these values.

    \n\nEffective Armour\nThis graph shows the effective armour for each damage type, found by dividing the raw armour value by the damage taken for that type.

    \n\n

    Keyboard Shortcuts

    \n
    \n
    Ctrl-b
    toggle boost
    \n
    Ctrl-e
    open export dialogue (outfitting page only)
    \n
    Ctrl-h
    open help dialogue
    \n
    Ctrl-i
    open import dialogue
    \n
    Ctrl-o
    open shortlink dialogue
    \n
    Ctrl-left-arrow
    increase SYS capacitor
    \n
    Ctrl-up-arrow
    increase ENG capacitor
    \n
    Ctrl-right-arrow
    increase WEP capacitor
    \n
    Ctrl-down-arrow
    reset power distributor
    \n
    Esc
    close any open dialogue
    \n
    \n

    Glossary

    \n
    \n
    Absolute damage
    A type of damage, without any protection. Absolute damage is always dealt at 100% regardless of if the damage is to shields, hull or modules, and irrespective of resistances
    \n
    DPS
    Damage per second; the amount of damage that a weapon or a ship can deal per second to a target under optimum conditions
    \n
    EPS
    Energy per second; the amount of energy that a weapon or a ship drains from the weapons capacitor per second when firing
    \n
    HPS
    Heat per second; the amount of heat that a weapon or a ship generates per second when firing
    \n
    Effectivness
    A comparison of the maximum DPS of a given weapon to the actual DPS of the given weapon in a specific situation. DPS can be reduced by range to the target, the target's hull and shield resistances, and the target's hardness
    \n
    Explosive damage
    A type of damage, protected against by explosive resistance
    \n
    Hardness
    The inherent resistance to damage of a ship's hull. Hardness is defined on a per-ship basis and there is currently nothing that can be done to change it. Hardness of a ship's hull is compared to the piercing of weapons: if piercing is higher than hardness the weapon does 100% damage, otherwise it does a fraction of its damage calculated as piercing/hardness
    \n
    Falloff
    The distance at which a weapons starts to do less damage than its stated DPS
    \n
    Kinetic damage
    A type of damage, protected against by kinetic resistance
    \n
    SDPS
    Sustained damage per second; the amount of damage that a weapon or a ship can deal per second to a target, taking in to account ammunition reload
    \n
    SEPS
    Sustained energy per second; the amount of energy that a weapon or a ship drains from the weapons capacitor per second when firing, taking in to account ammunition reload
    \n
    SHPS
    Sustained heat per second; the amount of heat that a weapon or a ship generates per second when firing, taking in to account ammunition reload
    \n
    Thermal damage
    A type of damage, protected against by thermal resistance
    \n
    \n\n " -} \ No newline at end of file +{ + "PHRASE_ALT_ALL": "Alt + Click to fill all slots", + "PHRASE_BACKUP_DESC": "Backup of all Coriolis data to save or transfer to another browser/device", + "PHRASE_CONFIRMATION": "Are you sure?", + "PHRASE_EXPORT_DESC": "A detailed JSON export of your build for use in other sites and tools", + "PHRASE_FASTEST_RANGE": "Consecutive max range jumps", + "PHRASE_IMPORT": "Paste JSON or import here", + "PHRASE_LADEN": "Ship mass + fuel + cargo", + "PHRASE_NO_BUILDS": "No builds added to comparison!", + "PHRASE_NO_RETROCH": "No Retrofitting changes", + "PHRASE_SELECT_BUILDS": "Select builds to compare", + "PHRASE_SG_RECHARGE": "Time from 50% to 100% charge, assuming full SYS capacitor to start with", + "PHRASE_SG_RECOVER": "Time from 0% to 50% charge, assuming full SYS capacitor to start with", + "PHRASE_UNLADEN": "Ship mass excluding fuel and cargo", + "PHRASE_UPDATE_RDY": "Update Available! Click to refresh", + "PHRASE_ENGAGEMENT_RANGE": "The distance between your ship and its target", + "PHRASE_SELECT_BLUEPRINT": "Click to select a blueprint", + "PHRASE_BLUEPRINT_WORST": "Worst primary values for this blueprint", + "PHRASE_BLUEPRINT_FIFTY": "50% of full values for this blueprint", + "PHRASE_BLUEPRINT_SEVEN_FIVE": "75% of full values for this blueprint", + "PHRASE_BLUEPRINT_RANDOM": "Random selection between worst and best primary values for this blueprint", + "PHRASE_BLUEPRINT_BEST": "Best primary values for this blueprint", + "PHRASE_BLUEPRINT_EXTREME": "Best beneficial and worst detrimental primary values for this blueprint", + "PHRASE_BLUEPRINT_RESET": "Remove all modifications and blueprint", + "PHRASE_SELECT_SPECIAL": "Click to select an experimental effect", + "PHRASE_NO_SPECIAL": "No experimental effect", + "PHRASE_SHOPPING_LIST": "Stations that sell this build", + "PHRASE_SHOPPING_MATS": "Materials needed for this build", + "PHRASE_REFIT_SHOPPING_LIST": "Stations that sell required modules", + "PHRASE_TOTAL_EFFECTIVE_SHIELD": "Total amount of damage that can be taken from each damage type, if using all shield cells", + "PHRASE_TIME_TO_LOSE_SHIELDS": "Shields will hold for", + "PHRASE_TIME_TO_RECOVER_SHIELDS": "Shields will recover in", + "PHRASE_TIME_TO_RECHARGE_SHIELDS": "Shields will recharge in", + "PHRASE_SHIELD_SOURCES": "Breakdown of the supply of shield energy", + "PHRASE_EFFECTIVE_SHIELD": "Effective shield strength against different damage types", + "PHRASE_ARMOUR_SOURCES": "Breakdown of the supply of armour", + "PHRASE_EFFECTIVE_ARMOUR": "Effective armour strength against different damage types", + "PHRASE_DAMAGE_TAKEN": "% of raw damage taken for different damage types", + "PHRASE_TIME_TO_LOSE_ARMOUR": "Armour will hold for", + "PHRASE_MODULE_PROTECTION_EXTERNAL": "Protection for hardpoints", + "PHRASE_MODULE_PROTECTION_INTERNAL": "Protection for all other modules", + "PHRASE_SHIELD_DAMAGE": "Breakdown of sources for sustained DPS against shields", + "PHRASE_ARMOUR_DAMAGE": "Breakdown of sources for sustained DPS against armour", + "PHRASE_TIME_TO_REMOVE_SHIELDS": "Will remove shields in", + "TT_TIME_TO_REMOVE_SHIELDS": "With sustained fire by all weapons", + "PHRASE_TIME_TO_REMOVE_ARMOUR": "Will remove armour in", + "TT_TIME_TO_REMOVE_ARMOUR": "With sustained fire by all weapons", + "PHRASE_TIME_TO_DRAIN_WEP": "Will drain WEP in", + "TT_TIME_TO_DRAIN_WEP": "Time to drain WEP capacitor with all weapons firing", + "TT_TIME_TO_LOSE_SHIELDS": "Against sustained fire from all opponent's weapons", + "TT_TIME_TO_LOSE_ARMOUR": "Against sustained fire from all opponent's weapons", + "TT_MODULE_ARMOUR": "Armour protecting against module damage", + "TT_MODULE_PROTECTION_EXTERNAL": "Percentage of damage diverted from hardpoints to module reinforcement packages", + "TT_MODULE_PROTECTION_INTERNAL": "Percentage of damage diverted from non-hardpoint modules to module reinforcement packages", + "TT_EFFECTIVE_SDPS_SHIELDS": "Actual sustained DPS whilst WEP capacitor is not empty", + "TT_EFFECTIVENESS_SHIELDS": "Effectivness compared to hitting a 0-resistance target with 0 pips to SYS at 0m", + "TT_EFFECTIVE_SDPS_ARMOUR": "Actual sustained DPS whilst WEP capacitor is not empty", + "TT_EFFECTIVENESS_ARMOUR": "Effectivness compared to hitting a 0-resistance target at 0m", + "PHRASE_EFFECTIVE_SDPS_SHIELDS": "SDPS against shields", + "PHRASE_EFFECTIVE_SDPS_ARMOUR": "SDPS against armour", + "TT_SUMMARY_SPEED": "With full fuel tank and 4 pips to ENG", + "TT_SUMMARY_SPEED_NONFUNCTIONAL": "Thrusters powered off or over maximum mass with full fuel and cargo loads", + "TT_SUMMARY_BOOST": "With full fuel tank and 4 pips to ENG", + "TT_SUMMARY_BOOST_TIME": "Time between each boost with 4 pips to ENG", + "TT_SUMMARY_BOOST_NONFUNCTIONAL": "Power distributor not able to supply enough power to boost", + "TT_SUMMARY_SHIELDS": "Raw shield strength, including boosters", + "TT_SUMMARY_SHIELDS_SCB": "Raw shield strength, including boosters and SCBs", + "TT_SUMMARY_SHIELDS_NONFUNCTIONAL": "No shield generator or shield generator powered off", + "TT_SUMMARY_INTEGRITY": "Ship integrity, including bulkheads and hull reinforcement packages", + "TT_SUMMARY_HULL_MASS": "Mass of the hull prior to any modules being installed", + "TT_SUMMARY_UNLADEN_MASS": "Mass of the hull and modules prior to any fuel or cargo", + "TT_SUMMARY_LADEN_MASS": "Mass of the hull and modules with full fuel and cargo", + "TT_SUMMARY_DPS": "Damage per second with all weapons firing", + "TT_SUMMARY_EPS": "WEP capacitor consumed per second with all weapons firing", + "TT_SUMMARY_TTD": "Time to drain WEP capacitor with all weapons firing and 4 pips to WEP", + "TT_SUMMARY_MAX_SINGLE_JUMP": "Farthest possible jump range with no cargo and only enough fuel for the jump itself", + "TT_SUMMARY_UNLADEN_SINGLE_JUMP": "Farthest possible jump range with no cargo and a full fuel tank", + "TT_SUMMARY_LADEN_SINGLE_JUMP": "Farthest possible jump range with full cargo and a full fuel tank", + "TT_SUMMARY_UNLADEN_TOTAL_JUMP": "Farthest possible range with no cargo, a full fuel tank, and jumping as far as possible each time", + "TT_SUMMARY_LADEN_TOTAL_JUMP": "Farthest possible range with full cargo, a full fuel tank, and jumping as far as possible each time", + "HELP_MODIFICATIONS_MENU": "Click on a number to enter a new value, or drag along the bar for small changes", + "am": "Auto Field-Maintenance Unit", + "bh": "Bulkheads", + "bl": "Beam Laser", + "bsg": "Bi-Weave Shield Generator", + "c": "Cannon", + "cc": "Collector Limpet Controller", + "ch": "Chaff Launcher", + "cr": "Cargo Rack", + "cs": "Manifest Scanner", + "dc": "Docking Computer", + "ec": "Electronic Countermeasure", + "fc": "Fragment Cannon", + "fh": "Fighter Hangar", + "fi": "FSD Interdictor", + "fs": "Fuel Scoop", + "fsd": "Frame Shift Drive", + "ft": "Fuel Tank", + "fx": "Fuel Transfer Limpet Controller", + "hb": "Hatch Breaker Limpet Controller", + "hr": "Hull Reinforcement Package", + "hs": "Heat Sink Launcher", + "kw": "Kill Warrant Scanner", + "ls": "Life Support", + "mc": "Multi-cannon", + "axmc": "AX Multi-cannon", + "ml": "Mining Laser", + "mr": "Missile Rack", + "axmr": "AX Missile Rack", + "mrp": "Module Reinforcement Package", + "nl": "Mine Launcher", + "pa": "Plasma Accelerator", + "pas": "Planetary Approach Suite", + "pc": "Prospector Limpet Controller", + "pce": "Economy Class Passenger Cabin", + "pci": "Business Class Passenger Cabin", + "pcm": "First Class Passenger Cabin", + "pcq": "Luxury Passenger Cabin", + "pd": "power distributor", + "pl": "Pulse Laser", + "po": "Point Defence", + "pp": "Power Plant", + "gpp": "Guardian Hybrid Power Plant", + "gpd": "Guardian Hybrid Power Distributor", + "gpc": "Guardian Plasma Charger", + "ggc": "Guardian Gauss Cannon", + "gsrp": "Guardian Shield Reinforcement Package", + "gfsb": "Guardian Frame Shift Drive Booster", + "ghrp": "Guardian Hull Reinforcement Package", + "gmrp": "Guardian Module Reinforcement Package", + "tbsc": "Shock Cannon", + "gsc": "Guardian Shard Cannon", + "psg": "Prismatic Shield Generator", + "pv": "Planetary Vehicle Hangar", + "rf": "Refinery", + "rfl": "Remote Release Flak Launcher", + "rg": "Rail Gun", + "s": "Sensors", + "sb": "Shield Booster", + "sc": "Stellar Scanners", + "scb": "Shield Cell Bank", + "sfn": "Shutdown Field Neutraliser", + "sg": "Shield Generator", + "ss": "Surface Scanners", + "t": "thrusters", + "tp": "Torpedo Pylon", + "ul": "Burst Laser", + "Send To EDEngineer": "Send To EDEngineer", + "ws": "Frame Shift Wake Scanner", + "rpl": "Repair Limpet Controller", + "rcpl": "Recon Limpet Controller", + "xs": "Xeno Scanner", + "emptyrestricted": "empty (restricted)", + "damage dealt to": "Damage dealt to", + "damage received from": "Damage received from", + "against shields": "Against shields", + "against hull": "Against hull", + "total effective shield": "Total effective shield", + "ammunition": "Ammo", + "secs": "s", + "rebuildsperbay": "Rebuilds per bay", + "worst": "Worst", + "average": "Average", + "random": "Random", + "best": "Best", + "extreme": "Extreme", + "reset": "Reset", + "dpe": "Damage per MJ of energy", + "dps": "Damage per second", + "sdps": "Sustained damage per second", + "dpssdps": "Damage per second (sustained damage per second)", + "eps": "Energy per second", + "epsseps": "Energy per second (sustained energy per second)", + "hps": "Heat per second", + "hpsshps": "Heat per second (sustained heat per second)", + "damage by": "Damage by", + "damage from": "Damage from", + "shield cells": "Shield cells", + "recovery": "Recovery", + "recharge": "Recharge", + "engine pips": "Engine Pips", + "4b": "4 pips and boost", + "speed": "Speed", + "pitch": "Pitch", + "roll": "Roll", + "yaw": "Yaw", + "internal protection": "Internal protection", + "external protection": "External protection", + "engagement range": "Engagement range", + "boost time": "Boost time", + "total": "Total", + "ammo": "Ammunition maximum", + "boot": "Boot time", + "hacktime": "Hack time", + "brokenregen": "Broken regeneration rate", + "burst": "Burst", + "burstrof": "Burst rate of fire", + "clip": "Ammunition clip", + "damage": "Damage", + "distdraw": "Distributor draw", + "duration": "Duration", + "eff": "Efficiency", + "engcap": "Engines capacity", + "engrate": "Engines recharge rate", + "explres": "Explosive resistance", + "facinglimit": "Facing limit", + "hullboost": "Hull boost", + "hullreinforcement": "Hull reinforcement", + "integrity": "Integrity", + "jitter": "Jitter", + "kinres": "Kinetic resistance", + "maxfuel": "Maximum fuel per jump", + "mass": "Mass", + "optmass": "Optimal mass", + "optmul": "Optimal multiplier", + "pgen": "Power generation", + "piercing": "Piercing", + "power": "Power draw", + "protection": "Protection", + "range": "Range", + "ranget": "Range", + "regen": "Regeneration rate", + "reload": "Reload", + "rof": "Rate of fire", + "angle": "Scan angle", + "scanrate": "Scan rate", + "scantime": "Scan time", + "shield": "Shield", + "armour": "Armour", + "shieldboost": "Shield boost", + "shieldreinforcement": "Shield reinforcement", + "shotspeed": "Shot speed", + "spinup": "Spin up time", + "syscap": "Systems capacity", + "sysrate": "Systems recharge rate", + "thermload": "Thermal load", + "thermres": "Thermal resistance", + "wepcap": "Weapons capacity", + "weprate": "Weapons recharge rate", + "minmass_sg": "Minimum hull mass", + "optmass_sg": "Optimal hull mass", + "maxmass_sg": "Maximum hull mass", + "minmul_sg": "Minimum strength", + "optmul_sg": "Optimal strength", + "maxmul_sg": "Minimum strength", + "minmass_psg": "Minimum hull mass", + "optmass_psg": "Optimal hull mass", + "maxmass_psg": "Maximum hull mass", + "minmul_psg": "Minimum strength", + "optmul_psg": "Optimal strength", + "maxmul_psg": "Minimum strength", + "minmass_bsg": "Minimum hull mass", + "optmass_bsg": "Optimal hull mass", + "maxmass_bsg": "Maximum hull mass", + "minmul_bsg": "Minimum strength", + "optmul_bsg": "Optimal strength", + "maxmul_bsg": "Minimum strength", + "range_s": "Typical emission range", + "absolute": "Absolute", + "explosive": "Explosive", + "kinetic": "Kinetic", + "thermal": "Thermal", + "generator": "Generator", + "boosters": "Boosters", + "cells": "Cells", + "shield addition": "Shield Addition", + "jump addition": "Jump Addition", + "bulkheads": "Bulkheads", + "reinforcement": "Reinforcement", + "power and costs": "power and costs", + "costs": "costs", + "retrofit costs": "retrofit costs", + "reload costs": "reload costs", + "profiles": "profiles", + "engine profile": "engine profile", + "fsd profile": "fsd profile", + "movement profile": "movement profile", + "damage to opponent's shields": "damage to opponent's shields", + "damage to opponent's hull": "damage to opponent's hull", + "offence": "offence", + "defence": "defence", + "shield metrics": "shield metrics", + "raw shield strength": "raw shield strength", + "shield sources": "shield sources", + "damage taken": "damage taken", + "effective shield": "effective shield", + "armour metrics": "armour metrics", + "raw armour strength": "raw armour strength", + "armour sources": "armour sources", + "raw module armour": "raw module armour", + "effective armour": "effective armour", + "offence metrics": "offence metrics", + "defence metrics": "defence metrics", + "fuel carried": "fuel carried", + "cargo carried": "cargo carried", + "ship control": "ship control", + "opponent": "opponent", + "opponent's shields": "opponent's shields", + "opponent's armour": "opponent's armour", + "shield damage sources": "shield damage sources", + "armour damage sources": "armour damage sources", + "never": "never", + "stock": "stock", + "boost": "boost", + "federation rank 1": "Recruit", + "federation rank 2": "Cadet", + "federation rank 3": "Midshipman", + "federation rank 4": "Petty Officer", + "federation rank 5": "Chief Petty Officer", + "federation rank 6": "Warrant Officer", + "federation rank 7": "Ensign", + "federation rank 8": "Lieutenant", + "federation rank 9": "Lieutenant Commander", + "federation rank 10": "Post Commander", + "federation rank 11": "Post Captain", + "federation rank 12": "Rear Admiral", + "federation rank 13": "Vice Admiral", + "federation rank 14": "Admiral", + "federation rank required": "Minimum Federation rank required to purchase", + "empire rank 1": "Outsider", + "empire rank 2": "Serf", + "empire rank 3": "Master", + "empire rank 4": "Squire", + "empire rank 5": "Knight", + "empire rank 6": "Lord", + "empire rank 7": "Baron", + "empire rank 8": "Viscount", + "empire rank 9": "Count", + "empire rank 10": "Earl", + "empire rank 11": "Marquis", + "empire rank 12": "Duke", + "empire rank 13": "Prince", + "empire rank 14": "King", + "empire rank required": "Minimum Empire rank required to purchase", + "horizons": "Horizons", + "horizons required": "Only available to those who have purchased Elite Dangerous: Horizons", + "horizons early adoption": "Horizons Early Adopter", + "PHASE_UPLOAD_ORBIS": "Upload to orbis.zone (Currently in trial period.)", + "orbis username": "Username/email for orbis.zone", + "orbis password": "Password for orbis.zone", + "horizons early adoption required": "Only available to those who purchased Elite Dangerous: Horizons on PC before February 5, 2016 or on Xbox One before July 30, 2016", + "HELP_TEXT": "\n

    Introduction

    \nCoriolis is a ship builder for Elite: Dangerous. This help file provides you with the information you need to use Coriolis.\n\n

    Importing Your Ship Into Coriolis

    \nOften, you will want to start with your existing ship in Coriolis and see how particular changes might affect it, for example upgrading your FSD. There are a number of tools that can be used to import your ship without you having to create it manually. This has the added benefit of copying over any engineering modifications that have taken place as well.

    \n\n

    Importing Your Ship From EDDI

    \nTo import your ship from EDDI first ensure that your connection to the Frontier servers' companion API is working. To do this check the 'Companion App' tab where you should see \"Your connection to the companion app is operational\". If not then follow the instructions in the companion app tab in EDDI to connect to the Frontier servers.

    \n\nOnce you have a working companion API connection go to the 'Shipyard' tab. At the right-hand side of each ship is an 'Export to Coriolis' button that will open your default web browser in Coriolis with the ship's build.

    \n\nNote that Internet Explorer and Edge might not import correctly, due to their internal restrictions on URL length. If you find that this is the case then please change your default browser to Chrome.

    \n\nAlso, the imported information does not provide any data on the power priority or enabled status of your cargo hatch. Coriolis sets this item to have a power priority of \"5\" and to be disabled by default. You can change this after import in the Power Management section.

    \n\n

    Importing Your Ship From EDMC

    \nTo import your ship from EDMC once your connection to the Frontier servers' companion API is working go to 'Settings ->Configuration' and set the 'Preferred Shipyard' to 'Coriolis'. Once this is set up clicking on your ship in the main window will open your default web browser in Coriolis with the ship's build.

    \n\nNote that Internet Explorer and Edge might not import correctly, due to their internal restrictions on URL length. If you find that this is the case then please change your default browser to Chrome.

    \n\n

    Understanding And Using The Outfitting Panels

    \nThe outfitting page is where you will spend most of your time, and contains the information for your ship. Information on each of the panels is provided below.

    \n\n

    Key Values

    \nAlong the top of the screen are some of the key values for your build. This is a handy reference for the values, but more information is provided for the values in the further panels.

    \n\nHere, along with most places in Coriolis, acronyms will have tooltips explaining what they mean. Hover over the acronym to obtain more detail, or look in the glossary at the end of this help.

    \n\nAll values are the highest possible, assuming that you an optimal setup for that particular value (maximum pips in ENG for speed, minimum fuel for jump range, etc.). This means that these values will not be affected by changes to pip settings. Details of the specific setup for each value are listed in the associated tootip.

    \n\n

    Modules

    \nThe next set of panels laid out horizontally across the screen contain the modules you have put in your build. From left to right these are the core modules, the internal modules, the hardpoints and the utility mounts. These represent the available slots in your ship and cannot be altered. Each slot has a class, or size, and in general any module up to a given size can fit in a given slot (exceptions being bulkheads, life support and sensors in core modules and restricted internal slots, which can only take a subset of module depending on their restrictions).

    \n\nTo add a module to a slot left-click on the slot and select the required module. Only the modules capable of fitting in the selected slot will be shown.

    \n\nTo remove a module from a slot right-click on the module.

    \n\nTo move a module from one slot to another drag it. If you instead want to copy the module drag it whilst holding down the 'Alt' key.

    \n\nClicking on the headings for each set of modules gives you the ability to either select an overall role for your ship (when clicking the core internal header) or a specific module with which you want to fill all applicable slots (when clicking the other headers).

    \n\n

    Ship Controls

    \nThe ship controls allow you to set your pips, boost, and amount of fuel and cargo that your build carries. The changes made here will effect the information supplied in the subsequent panels, giving you a clearer view of what effect different changing these items will have.

    \n\nShip control settings are saved as part of a build.

    \n\n

    Opponent

    \nThe opponet selection allows you to choose your opponent. The opponent can be either a stock build of a ship or one of your own saved builds. You can also set the engagement range between you and your opponent. Your selection here will effect the information supplied in the subsequent panels, specifically the Offence and Defence panels.

    \n\nOpponent settings are saved as part of a build.

    \n\n

    Power and Costs Sub-panels

    \n

    Power

    \nThe power management panel provides information about power usage and priorities. It allows you to enable and disable individual modules, as well as set power priorities for each module. Disabled modules will not be included in the build's statistics, with the exception of Shield Cell Banks as they are usually disabled when not in use and only enabled when required.

    \n\n

    Costs

    \nThe costs panel provides information about the costs for each of your modules, and the total cost and insurance for your build. By default Coriolis uses the standard costs, however discounts for your ship, modules and insurance can be altered in the 'Settings' at the top-right of the page.

    \n\nThe retrofit costs provides information about the costs of changing the base build for your ship, or your saved build, to the current build.

    \n\nThe reload costs provides information about the costs of reloading your current build.

    \n\n

    Profiles

    \nProfiles provide graphs that show the general performance of modules in your build\n\n

    Engine Profile

    \nThe engine profile panel provides information about the capabilities of your current thrusters. The graph shows you how the maximum speed alters with the overall mass of your build. The vertical dashed line on the graph shows your current mass. Your engine profile can be altered by obtaining different thrusters or engineering your existing thrusters, and you can increase your maximum speed by adding pips to the ENG capacitor as well as reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build. You can also temporarily increase your speed by hitting the boost button.

    \n\n

    FSD Profile

    \nThe FSD profile panel provides information about the capabilities of your current frame shift drive. The graph shows you how the maximum jump range alters with the overall mass of your build. The vertical dashed line on the graph shows your current maximum single jump range. Your FSD profile can be altered by obtaining a different FSD or engineering your existing FSD, and you can increase your maximum jump range by reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build,

    \n\n

    Movement Profile

    \nThe movement profile panel provides information about the capabilities of your current thrusters with your current overall mass and ENG pips settings. The diagram shows your ability to move and rotate in the different axes:\n\n
    \n
    Speed
    The fastest the ship can move, in metres per second
    \n
    Pitch
    The fastest the ship can raise or lower its nose, in degrees per second
    \n
    Roll
    The fastest the ship can roll its body, in degrees per second
    \n
    Yaw
    The fastest the ship can turn its nose left or right, in degrees per second
    \n
    \n\nYour movement profile can be altered by obtaining different thrusters or engineering your existing thrusters, and you can increase your movement values by adding pips to the ENG capacitor as well as reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build. You can also temporarily increase your movement profile by hitting the boost button.

    \n\n

    Damage Profile

    \nThe damage profile provides two graphs showing how the the build's damage to the opponent's shields and hull change with engagement range. The vertical dashed line on the graph shows your current engagement range. This combines information about the build's weapons with the opponent's shields and hull to provide an accurate picture of sustained damage that can be inflicted on the opponent.

    \n\n

    Offence

    \n

    Summary

    \nThe offence summary provides per-weapon information about sustained damage per second inflicted to shields and hull, along with a measure of effectiveness of that weapon. The effectiveness value has a tooltip that provides a breakdown of the effectiveness, and can include reductions or increases due to range, resistance, and either power distributor (for shields) or hardness (for hull). The final effectiveness value is calculated by multiplying these percentages together.

    \n\n

    Offence Metrics

    \nThe offence metrics panel provides information about your offence.

    \n\nTime to drain is a measure of how quickly your WEP capacitor will drain when firing all weapons. It is affected by the number of pips you have in your WEP capacitor, with more pips resulting in a higher WEP recharge rate and hence a longer time to drain.

    \n\nThe next value is the time it will take you to remove your opponent's shields. This assumes that you have 100% time on target and that your engagement range stays constant. Note that if your time to remove shields is longer than your time to drain this assumes that you continue firing throughout, inflicting lower damage due to the reduced energy in your WEP capacitor.

    \n\nThe next value is the time it will take you to remove your opponent's armour. This follows the same logic as the time to remove shields.

    \n\n

    Shield Damage Sources

    \nThe shield damage sources provides information about the sources of damage to your opponent by damage type. For each applicable type of damage (absolute explosive, kinetic, thermal) a sustained damage per second value is provided.

    \n\n

    Hull Damage Sources

    \nThe hull damage sources provides information about the sources of damage to your opponent by damage type. For each applicable type of damage (absolute explosive, kinetic, thermal) a sustained damage per second value is provided.

    \n\n

    Defence

    \n

    Shield Metrics

    \nThe shield metrics provides information about your shield defence.

    \n\nRaw shield strength is the sum of the shield from your generator, boosters and shield cell banks. A tooltip provides a breakdown of these values.

    \n\nThe time the shields will hold for is the time it will take your opponent' to remove your shields. This assumes that they have 100% time on target and that the engagement range stays constant. It also assumes that you fire all of your shield cell banks prior to your shields being lost.

    \n\nThe time the shields will recover in is the time it will take your shields to go from collapsed (0%) to recovered (50%). This is affected by the number of pips you have in your SYS capacitor.

    \n\nThe time the shields will recharge in is the time it will take your shields to go from recovered (50%) to full (100%). This is affected by the number of pips you have in your SYS capacitor.

    \n\nShield Sources\nThis chart provides information about the sources of your shields. For each applicable source of shields (generator, boosters, shield cell banks) a value is provided.

    \n\nDamage Taken\nThis graph shows how the initial damage from the weapons of each type are reduced before their damage is applied to the shields. For each type of damage (absolute, explosive, kinetic, thermal) a percentage of the initial damage is provided. A tooltip provides a breakdown of these values.

    \n\nEffective Shield\nThis graph shows the effective shield for each damage type, found by dividing the raw shield value by the damage taken for that type.

    \n\n

    Amour Metrics

    \nThe armour metrics provides information about your armour defence.

    \n\nRaw armour strength is the sum of the armour from your bulkheads and hull reinforcement packages. A tooltip provides a breakdown of these values.

    \n\nThe time the armour will hold for is the time it will take your opponent' to take your armour to 0. This assumes that they have 100% time on target, the engagement range stays constant, and that all damage is dealt to the armour rather than modules.

    \n\nRaw module armour is the sum of the protection from your module reinforcement packages.

    \n\nProtection for hardpoints is the amount of protection that your module reinforcement packages provide to hardpoints. This percentage of damage to the hardpoints will be diverted to the module reinforcement packages.

    \n\nProtection for all other modules is the amount of protection that your module reinforcement packages provide to everything other than hardpoints. This percentage of damage to the modules will be diverted to the module reinforcement packages.

    \n\nArmour Sources\nThis chart provides information about the sources of your armour. For each applicable source of shields (bulkheads, hull reinforcement packages) a value is provided.

    \n\nDamage Taken\nThis graph shows how the initial damage from the weapons of each type are reduced before their damage is applied to the armour. For each type of damage (absolute, explosive, kinetic, thermal) a percentage of the initial damage is provided. A tooltip provides a breakdown of these values.

    \n\nEffective Armour\nThis graph shows the effective armour for each damage type, found by dividing the raw armour value by the damage taken for that type.

    \n\n

    Keyboard Shortcuts

    \n
    \n
    Ctrl-b
    toggle boost
    \n
    Ctrl-e
    open export dialogue (outfitting page only)
    \n
    Ctrl-h
    open help dialogue
    \n
    Ctrl-i
    open import dialogue
    \n
    Ctrl-o
    open shortlink dialogue
    \n
    Ctrl-left-arrow
    increase SYS capacitor
    \n
    Ctrl-up-arrow
    increase ENG capacitor
    \n
    Ctrl-right-arrow
    increase WEP capacitor
    \n
    Ctrl-down-arrow
    reset power distributor
    \n
    Esc
    close any open dialogue
    \n
    \n

    Glossary

    \n
    \n
    Absolute damage
    A type of damage, without any protection. Absolute damage is always dealt at 100% regardless of if the damage is to shields, hull or modules, and irrespective of resistances
    \n
    DPS
    Damage per second; the amount of damage that a weapon or a ship can deal per second to a target under optimum conditions
    \n
    EPS
    Energy per second; the amount of energy that a weapon or a ship drains from the weapons capacitor per second when firing
    \n
    HPS
    Heat per second; the amount of heat that a weapon or a ship generates per second when firing
    \n
    Effectivness
    A comparison of the maximum DPS of a given weapon to the actual DPS of the given weapon in a specific situation. DPS can be reduced by range to the target, the target's hull and shield resistances, and the target's hardness
    \n
    Explosive damage
    A type of damage, protected against by explosive resistance
    \n
    Hardness
    The inherent resistance to damage of a ship's hull. Hardness is defined on a per-ship basis and there is currently nothing that can be done to change it. Hardness of a ship's hull is compared to the piercing of weapons: if piercing is higher than hardness the weapon does 100% damage, otherwise it does a fraction of its damage calculated as piercing/hardness
    \n
    Falloff
    The distance at which a weapons starts to do less damage than its stated DPS
    \n
    Kinetic damage
    A type of damage, protected against by kinetic resistance
    \n
    SDPS
    Sustained damage per second; the amount of damage that a weapon or a ship can deal per second to a target, taking in to account ammunition reload
    \n
    SEPS
    Sustained energy per second; the amount of energy that a weapon or a ship drains from the weapons capacitor per second when firing, taking in to account ammunition reload
    \n
    SHPS
    Sustained heat per second; the amount of heat that a weapon or a ship generates per second when firing, taking in to account ammunition reload
    \n
    Thermal damage
    A type of damage, protected against by thermal resistance
    \n
    \n\n " +} diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx index 16c7f500..062b3415 100644 --- a/src/app/pages/OutfittingPage.jsx +++ b/src/app/pages/OutfittingPage.jsx @@ -1,684 +1,716 @@ -import React from 'react'; -// import Perf from 'react-addons-perf'; -import { Ships } from 'coriolis-data/dist'; -import cn from 'classnames'; -import Page from './Page'; -import Router from '../Router'; -import Persist from '../stores/Persist'; -import * as Utils from '../utils/UtilityFunctions'; -import Ship from '../shipyard/Ship'; -import { toDetailedBuild } from '../shipyard/Serializer'; -import { outfitURL } from '../utils/UrlGenerators'; -import { FloppyDisk, Bin, Switch, Download, Reload, LinkIcon, ShoppingIcon, MatIcon } from '../components/SvgIcons'; -import LZString from 'lz-string'; -import ShipSummaryTable from '../components/ShipSummaryTable'; -import StandardSlotSection from '../components/StandardSlotSection'; -import HardpointSlotSection from '../components/HardpointSlotSection'; -import InternalSlotSection from '../components/InternalSlotSection'; -import UtilitySlotSection from '../components/UtilitySlotSection'; -import Pips from '../components/Pips'; -import Boost from '../components/Boost'; -import Fuel from '../components/Fuel'; -import Cargo from '../components/Cargo'; -import ShipPicker from '../components/ShipPicker'; -import EngagementRange from '../components/EngagementRange'; -import OutfittingSubpages from '../components/OutfittingSubpages'; -import ModalExport from '../components/ModalExport'; -import ModalPermalink from '../components/ModalPermalink'; -import ModalShoppingList from '../components/ModalShoppingList'; - -/** - * Document Title Generator - * @param {String} shipName Ship Name - * @param {String} buildName Build Name - * @return {String} Document title - */ -function getTitle(shipName, buildName) { - return buildName ? buildName : shipName; -} - -/** - * The Outfitting Page - */ -export default class OutfittingPage extends Page { - - /** - * Constructor - * @param {Object} props React Component properties - * @param {Object} context React Component context - */ - constructor(props, context) { - super(props, context); - // window.Perf = Perf; - this.state = this._initState(props, context); - this._keyDown = this._keyDown.bind(this); - this._exportBuild = this._exportBuild.bind(this); - this._pipsUpdated = this._pipsUpdated.bind(this); - this._boostUpdated = this._boostUpdated.bind(this); - this._cargoUpdated = this._cargoUpdated.bind(this); - this._fuelUpdated = this._fuelUpdated.bind(this); - this._opponentUpdated = this._opponentUpdated.bind(this); - this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this); - this._sectionMenuRefs = {}; - } - - /** - * [Re]Create initial state from context - * @param {Object} props React component properties - * @param {context} context React component context - * @return {Object} New state object - */ - _initState(props, context) { - let params = context.route.params; - let shipId = params.ship; - let code = params.code; - let buildName = params.bn; - let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults - let savedCode = Persist.getBuild(shipId, buildName); - if (!data) { - return { error: { message: 'Ship not found: ' + shipId } }; - } - let ship = new Ship(shipId, data.properties, data.slots); // Create a new Ship instance - if (code) { - ship.buildFrom(code); // Populate modules from serialized 'code' URL param - } else { - ship.buildWith(data.defaults); // Populate with default components - } - - this._getTitle = getTitle.bind(this, data.properties.name); - - // Obtain ship control from code - const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = this._obtainControlFromCode(ship, code); - return { - error: null, - title: this._getTitle(buildName), - costTab: Persist.getCostTab() || 'costs', - buildName, - newBuildName: buildName, - shipId, - ship, - code, - savedCode, - sys, - eng, - wep, - boost, - fuel, - cargo, - opponent, - opponentBuild, - opponentSys, - opponentEng, - opponentWep, - engagementRange - }; - } - - /** - * Handle build name change and update state - * @param {SyntheticEvent} event React Event - */ - _buildNameChange(event) { - let stateChanges = { - newBuildName: event.target.value - }; - - if (Persist.hasBuild(this.state.shipId, stateChanges.newBuildName)) { - stateChanges.savedCode = Persist.getBuild(this.state.shipId, stateChanges.newBuildName); - } else { - stateChanges.savedCode = null; - } - - this.setState(stateChanges); - } - - /** - * Update the control part of the route - */ - _updateRouteOnControlChange() { - const { ship, shipId, buildName } = this.state; - const code = this._fullCode(ship); - this._updateRoute(shipId, buildName, code); - this.setState({ code }); - } - - /** - * Provide a full code for this ship, including any additions due to the outfitting page - * @param {Object} ship the ship - * @param {number} fuel the fuel carried by the ship (if different from that in state) - * @param {number} cargo the cargo carried by the ship (if different from that in state) - * @returns {string} the code for this ship - */ - _fullCode(ship, fuel, cargo) { - return `${ship.toString()}.${LZString.compressToBase64(this._controlCode(fuel, cargo))}`; - } - - /** - * Obtain the control information from the build code - * @param {Object} ship The ship - * @param {string} code The build code - * @returns {Object} The control information - */ - _obtainControlFromCode(ship, code) { - // Defaults - let sys = 2; - let eng = 2; - let wep = 2; - let boost = false; - let fuel = ship.fuelCapacity; - let cargo = ship.cargoCapacity; - let opponent = new Ship('eagle', Ships['eagle'].properties, Ships['eagle'].slots).buildWith(Ships['eagle'].defaults); - let opponentSys = 2; - let opponentEng = 2; - let opponentWep = 2; - let opponentBuild; - let engagementRange = 1000; - - // Obtain updates from code, if available - if (code) { - const parts = code.split('.'); - if (parts.length >= 5) { - // We have control information in the code - const control = LZString.decompressFromBase64(Utils.fromUrlSafe(parts[4])).split('/'); - sys = parseFloat(control[0]); - eng = parseFloat(control[1]); - wep = parseFloat(control[2]); - boost = control[3] == 1 ? true : false; - fuel = parseFloat(control[4]); - cargo = parseInt(control[5]); - if (control[6]) { - const shipId = control[6]; - opponent = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots); - if (control[7] && Persist.getBuild(shipId, control[7])) { - // Ship is a particular build - const opponentCode = Persist.getBuild(shipId, control[7]); - opponent.buildFrom(opponentCode); - opponentBuild = control[7]; - if (opponentBuild) { - // Obtain opponent's sys/eng/wep pips from their code - const opponentParts = opponentCode.split('.'); - if (opponentParts.length >= 5) { - const opponentControl = LZString.decompressFromBase64(Utils.fromUrlSafe(opponentParts[4])).split('/'); - opponentSys = parseFloat(opponentControl[0]); - opponentEng = parseFloat(opponentControl[1]); - opponentWep = parseFloat(opponentControl[2]); - } - } - } else { - // Ship is a stock build - opponent.buildWith(Ships[shipId].defaults); - } - } - engagementRange = parseInt(control[8]); - } - } - - return { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange }; - } - - /** - * Triggered when pips have been updated - * @param {number} sys SYS pips - * @param {number} eng ENG pips - * @param {number} wep WEP pips - */ - _pipsUpdated(sys, eng, wep) { - this.setState({ sys, eng, wep }, () => this._updateRouteOnControlChange()); - } - - /** - * Triggered when boost has been updated - * @param {boolean} boost true if boosting - */ - _boostUpdated(boost) { - this.setState({ boost }, () => this._updateRouteOnControlChange()); - } - - /** - * Triggered when fuel has been updated - * @param {number} fuel the amount of fuel, in T - */ - _fuelUpdated(fuel) { - this.setState({ fuel }, () => this._updateRouteOnControlChange()); - } - - /** - * Triggered when cargo has been updated - * @param {number} cargo the amount of cargo, in T - */ - _cargoUpdated(cargo) { - this.setState({ cargo }, () => this._updateRouteOnControlChange()); - } - - /** - * Triggered when engagement range has been updated - * @param {number} engagementRange the engagement range, in m - */ - _engagementRangeUpdated(engagementRange) { - this.setState({ engagementRange }, () => this._updateRouteOnControlChange()); - } - - /** - * Triggered when target ship has been updated - * @param {string} opponent the opponent's ship model - * @param {string} opponentBuild the name of the opponent's build - */ - _opponentUpdated(opponent, opponentBuild) { - const opponentShip = new Ship(opponent, Ships[opponent].properties, Ships[opponent].slots); - let opponentSys = this.state.opponentSys; - let opponentEng = this.state.opponentEng; - let opponentWep = this.state.opponentWep; - if (opponentBuild && Persist.getBuild(opponent, opponentBuild)) { - // Ship is a particular build - opponentShip.buildFrom(Persist.getBuild(opponent, opponentBuild)); - // Set pips for opponent - const opponentParts = Persist.getBuild(opponent, opponentBuild).split('.'); - if (opponentParts.length >= 5) { - const opponentControl = LZString.decompressFromBase64(Utils.fromUrlSafe(opponentParts[4])).split('/'); - opponentSys = parseFloat(opponentControl[0]); - opponentEng = parseFloat(opponentControl[1]); - opponentWep = parseFloat(opponentControl[2]); - } - } else { - // Ship is a stock build - opponentShip.buildWith(Ships[opponent].defaults); - opponentSys = 2; - opponentEng = 2; - opponentWep = 2; - } - - this.setState({ opponent: opponentShip, opponentBuild, opponentSys, opponentEng, opponentWep }, () => this._updateRouteOnControlChange()); - } - - /** - * Set the control code for this outfitting page - * @param {number} fuel the fuel carried by the ship (if different from that in state) - * @param {number} cargo the cargo carried by the ship (if different from that in state) - * @returns {string} The control code - */ - _controlCode(fuel, cargo) { - const { sys, eng, wep, boost, opponent, opponentBuild, engagementRange } = this.state; - const code = `${sys}/${eng}/${wep}/${boost ? 1 : 0}/${fuel || this.state.fuel}/${cargo || this.state.cargo}/${opponent.id}/${opponentBuild ? opponentBuild : ''}/${engagementRange}`; - return code; - } - - /** - * Save the current build - */ - _saveBuild() { - const { ship, buildName, newBuildName, shipId } = this.state; - - // If this is a stock ship the code won't be set, so ensure that we have it - const code = this.state.code || ship.toString(); - - Persist.saveBuild(shipId, newBuildName, code); - this._updateRoute(shipId, newBuildName, code); - - let opponent, opponentBuild, opponentSys, opponentEng, opponentWep; - if (shipId === this.state.opponent.id && buildName === this.state.opponentBuild) { - // This is a save of our current opponent build; update it - opponentBuild = newBuildName; - opponent = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots).buildFrom(code); - opponentSys = this.state.sys; - opponentEng = this.state.eng; - opponentWep = this.state.wep; - } else { - opponentBuild = this.state.opponentBuild; - opponent = this.state.opponent; - opponentSys = this.state.opponentSys; - opponentEng = this.state.opponentEng; - opponentWep = this.state.opponentWep; - } - this.setState({ buildName: newBuildName, code, savedCode: code, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, title: this._getTitle(newBuildName) }); - } - - /** - * Rename the current build - */ - _renameBuild() { - const { code, buildName, newBuildName, shipId, ship } = this.state; - if (buildName != newBuildName && newBuildName.length) { - Persist.deleteBuild(shipId, buildName); - Persist.saveBuild(shipId, newBuildName, code); - this._updateRoute(shipId, newBuildName, code); - this.setState({ buildName: newBuildName, code, savedCode: code, opponentBuild: newBuildName }); - } - } - - /** - * Reload build from last save - */ - _reloadBuild() { - this.setState({ code: this.state.savedCode }, () => this._codeUpdated()); - } - - /** - * Reset build to Stock/Factory defaults - */ - _resetBuild() { - const { ship, shipId, buildName } = this.state; - // Rebuild ship - ship.buildWith(Ships[shipId].defaults); - // Reset controls - const code = ship.toString(); - const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code); - // Update state, and refresh the ship - this.setState({ - sys, - eng, - wep, - boost, - fuel, - cargo, - opponent, - opponentBuild, - engagementRange - }, () => this._updateRoute(shipId, buildName, code)); - } - - /** - * Delete the build - */ - _deleteBuild() { - const { shipId, buildName } = this.state; - Persist.deleteBuild(shipId, buildName); - - let opponentBuild; - if (shipId === this.state.opponent.id && buildName === this.state.opponentBuild) { - // Our current opponent has been deleted; revert to stock - opponentBuild = null; - } else { - opponentBuild = this.state.opponentBuild; - } - Router.go(outfitURL(this.state.shipId)); - - this.setState({ opponentBuild }); - } - - /** - * Serialized and show the export modal - */ - _exportBuild() { - let translate = this.context.language.translate; - let { buildName, ship } = this.state; - this.context.showModal(); - } - - /** - * Called when the code for the ship has been updated, to synchronise the rest of the data - */ - _codeUpdated() { - const { code, ship, shipId, buildName } = this.state; - - // Rebuild ship from the code - this.state.ship.buildFrom(code); - - // Obtain controls from the code - const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code); - // Update state, and refresh the route when complete - this.setState({ - sys, - eng, - wep, - boost, - fuel, - cargo, - opponent, - opponentBuild, - engagementRange - }, () => this._updateRoute(shipId, buildName, code)); - } - - /** - * Called when the ship has been updated, to set the code and then update accordingly - */ - _shipUpdated() { - let { ship, shipId, buildName, cargo, fuel } = this.state; - if (cargo > ship.cargoCapacity) { - cargo = ship.cargoCapacity; - } - if (fuel > ship.fuelCapacity) { - fuel = ship.fuelCapacity; - } - const code = this._fullCode(ship, fuel, cargo); - // Only update the state if this really has been updated - if (this.state.code != code || this.state.cargo != cargo || this.state.fuel != fuel) { - this.setState({ code, cargo, fuel }, () => this._updateRoute(shipId, buildName, code)); - } - } - - /** - * Update the current route based on build - * @param {string} shipId Ship Id - * @param {string} buildName Current build name - * @param {string} code Serialized ship 'code' - */ - _updateRoute(shipId, buildName, code) { - Router.replace(outfitURL(shipId, code, buildName)); - } - - /** - * Update state based on context changes - * @param {Object} nextProps Incoming/Next properties - * @param {Object} nextContext Incoming/Next conext - */ - componentWillReceiveProps(nextProps, nextContext) { - if (this.context.route !== nextContext.route) { // Only reinit state if the route has changed - this.setState(this._initState(nextProps, nextContext)); - } - } - - /** - * Add listeners when about to mount - */ - componentWillMount() { - document.addEventListener('keydown', this._keyDown); - } - - /** - * Remove listeners on unmount - */ - componentWillUnmount() { - document.removeEventListener('keydown', this._keyDown); - } - - /** - * Generates the short URL - */ - _genShortlink() { - this.context.showModal(); - } - - /** - * Open up a window for EDDB with a shopping list of our components - */ - _eddbShoppingList() { - const ship = this.state.ship; - - const shipId = Ships[ship.id].eddbID; - // Provide unique list of non-PP module EDDB IDs - const modIds = ship.internal.concat(ship.bulkheads, ship.standard, ship.hardpoints).filter(slot => slot !== null && slot.m !== null && !slot.m.pp).map(slot => slot.m.eddbID).filter((v, i, a) => a.indexOf(v) === i); - - // Open up the relevant URL - window.open('https://eddb.io/station?s=' + shipId + '&m=' + modIds.join(',')); - } - - /** - * Generates the shopping list - */ - _genShoppingList() { - this.context.showModal(); - } - - /** - * Handle Key Down - * @param {Event} e Keyboard Event - */ - _keyDown(e) { - // .keyCode will eventually be replaced with .key - switch (e.keyCode) { - case 69: // 'e' - if (e.ctrlKey || e.metaKey) { // CTRL/CMD + e - e.preventDefault(); - this._exportBuild(); - } - break; - } - } - - /** - * Render the Page - * @return {React.Component} The page contents - */ - renderPage() { - let state = this.state, - { language, termtip, tooltip, sizeRatio, onWindowResize } = this.context, - { translate, units, formats } = language, - { ship, code, savedCode, buildName, newBuildName, sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = state, - hide = tooltip.bind(null, null), - menu = this.props.currentMenu, - shipUpdated = this._shipUpdated, - canSave = (newBuildName || buildName) && code !== savedCode, - canRename = buildName && newBuildName && buildName != newBuildName, - canReload = savedCode && canSave; - - // Code can be blank for a default loadout. Prefix it with the ship name to ensure that changes in default ships is picked up - code = ship.name + (code || ''); - - // Markers are used to propagate state changes without requiring a deep comparison of the ship, as that takes a long time - const _sStr = ship.getStandardString(); - const _iStr = ship.getInternalString(); - const _hStr = ship.getHardpointsString(); - const _pStr = `${ship.getPowerEnabledString()}${ship.getPowerPrioritiesString()}`; - const _mStr = ship.getModificationsString(); - - const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`; - const internalSlotMarker = `${ship.name}${_iStr}${_pStr}${_mStr}`; - const hardpointsSlotMarker = `${ship.name}${_hStr}${_pStr}${_mStr}`; - const boostMarker = `${ship.canBoost(cargo, fuel)}`; - const shipSummaryMarker = `${ship.name}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`; - - const requirements = Ships[ship.id].requirements; - let requirementElements = []; - /** - * Render the requirements for a ship / etc - * @param {string} className Class names - * @param {string} textKey The key for translating - * @param {String} tooltipTextKey Tooltip key - */ - function renderRequirement(className, textKey, tooltipTextKey) { - if (textKey.startsWith('empire') || textKey.startsWith('federation')) { - requirementElements.push(
    {translate(textKey)}
    ); - } else { - requirementElements.push(
    {translate(textKey)}
    ); - } - } - - if (requirements) { - requirements.federationRank && renderRequirement('federation', 'federation rank ' + requirements.federationRank, 'federation rank required'); - requirements.empireRank && renderRequirement('empire', 'empire rank ' + requirements.empireRank, 'empire rank required'); - requirements.horizons && renderRequirement('horizons', 'horizons', 'horizons required'); - requirements.horizonsEarlyAdoption && renderRequirement('horizons', 'horizons early adoption', 'horizons early adoption required'); - } - - return ( -
    -
    -

    {ship.name}

    -
    {requirementElements}
    -
    - - - - - - - - - - -
    -
    - - {/* Main tables */} - - - - - - - {/* Control of ship and opponent */} -
    -
    -

    {translate('ship control')}

    -
    -
    - -
    -
    -
    - -
    -
    - -
    -
    - { ship.cargoCapacity > 0 ? : null } -
    -
    -
    -

    {translate('opponent')}

    -
    -
    - -
    -
    -
    - -
    - - {/* Tabbed subpages */} - -
    - ); - } -} +import React from 'react'; +// import Perf from 'react-addons-perf'; +import { Ships } from 'coriolis-data/dist'; +import cn from 'classnames'; +import Page from './Page'; +import Router from '../Router'; +import Persist from '../stores/Persist'; +import * as Utils from '../utils/UtilityFunctions'; +import Ship from '../shipyard/Ship'; +import * as _ from 'lodash'; +import { toDetailedBuild } from '../shipyard/Serializer'; +import { outfitURL } from '../utils/UrlGenerators'; +import { + FloppyDisk, + Bin, + Switch, + Download, + Reload, + LinkIcon, + ShoppingIcon, + MatIcon, + OrbisIcon +} from '../components/SvgIcons'; +import LZString from 'lz-string'; +import ShipSummaryTable from '../components/ShipSummaryTable'; +import StandardSlotSection from '../components/StandardSlotSection'; +import HardpointSlotSection from '../components/HardpointSlotSection'; +import InternalSlotSection from '../components/InternalSlotSection'; +import UtilitySlotSection from '../components/UtilitySlotSection'; +import Pips from '../components/Pips'; +import Boost from '../components/Boost'; +import Fuel from '../components/Fuel'; +import Cargo from '../components/Cargo'; +import ShipPicker from '../components/ShipPicker'; +import EngagementRange from '../components/EngagementRange'; +import OutfittingSubpages from '../components/OutfittingSubpages'; +import ModalExport from '../components/ModalExport'; +import ModalPermalink from '../components/ModalPermalink'; +import ModalShoppingList from '../components/ModalShoppingList'; +import ModalOrbis from '../components/ModalOrbis'; + +/** + * Document Title Generator + * @param {String} shipName Ship Name + * @param {String} buildName Build Name + * @return {String} Document title + */ +function getTitle(shipName, buildName) { + return buildName ? buildName : shipName; +} + +/** + * The Outfitting Page + */ +export default class OutfittingPage extends Page { + + /** + * Constructor + * @param {Object} props React Component properties + * @param {Object} context React Component context + */ + constructor(props, context) { + super(props, context); + // window.Perf = Perf; + this.state = this._initState(props, context); + this._keyDown = this._keyDown.bind(this); + this._exportBuild = this._exportBuild.bind(this); + this._pipsUpdated = this._pipsUpdated.bind(this); + this._boostUpdated = this._boostUpdated.bind(this); + this._cargoUpdated = this._cargoUpdated.bind(this); + this._fuelUpdated = this._fuelUpdated.bind(this); + this._opponentUpdated = this._opponentUpdated.bind(this); + this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this); + this._sectionMenuRefs = {}; + } + + /** + * [Re]Create initial state from context + * @param {Object} props React component properties + * @param {context} context React component context + * @return {Object} New state object + */ + _initState(props, context) { + let params = context.route.params; + let shipId = params.ship; + let code = params.code; + let buildName = params.bn; + let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults + let savedCode = Persist.getBuild(shipId, buildName); + if (!data) { + return { error: { message: 'Ship not found: ' + shipId } }; + } + let ship = new Ship(shipId, data.properties, data.slots); // Create a new Ship instance + if (code) { + ship.buildFrom(code); // Populate modules from serialized 'code' URL param + } else { + ship.buildWith(data.defaults); // Populate with default components + } + + this._getTitle = getTitle.bind(this, data.properties.name); + + // Obtain ship control from code + const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = this._obtainControlFromCode(ship, code); + return { + error: null, + title: this._getTitle(buildName), + costTab: Persist.getCostTab() || 'costs', + buildName, + newBuildName: buildName, + shipId, + ship, + code, + savedCode, + sys, + eng, + wep, + boost, + fuel, + cargo, + opponent, + opponentBuild, + opponentSys, + opponentEng, + opponentWep, + engagementRange + }; + } + + /** + * Handle build name change and update state + * @param {SyntheticEvent} event React Event + */ + _buildNameChange(event) { + let stateChanges = { + newBuildName: event.target.value + }; + + if (Persist.hasBuild(this.state.shipId, stateChanges.newBuildName)) { + stateChanges.savedCode = Persist.getBuild(this.state.shipId, stateChanges.newBuildName); + } else { + stateChanges.savedCode = null; + } + + this.setState(stateChanges); + } + + /** + * Update the control part of the route + */ + _updateRouteOnControlChange() { + const { ship, shipId, buildName } = this.state; + const code = this._fullCode(ship); + this._updateRoute(shipId, buildName, code); + this.setState({ code }); + } + + /** + * Provide a full code for this ship, including any additions due to the outfitting page + * @param {Object} ship the ship + * @param {number} fuel the fuel carried by the ship (if different from that in state) + * @param {number} cargo the cargo carried by the ship (if different from that in state) + * @returns {string} the code for this ship + */ + _fullCode(ship, fuel, cargo) { + return `${ship.toString()}.${LZString.compressToBase64(this._controlCode(fuel, cargo))}`; + } + + /** + * Obtain the control information from the build code + * @param {Object} ship The ship + * @param {string} code The build code + * @returns {Object} The control information + */ + _obtainControlFromCode(ship, code) { + // Defaults + let sys = 2; + let eng = 2; + let wep = 2; + let boost = false; + let fuel = ship.fuelCapacity; + let cargo = ship.cargoCapacity; + let opponent = new Ship('eagle', Ships['eagle'].properties, Ships['eagle'].slots).buildWith(Ships['eagle'].defaults); + let opponentSys = 2; + let opponentEng = 2; + let opponentWep = 2; + let opponentBuild; + let engagementRange = 1000; + + // Obtain updates from code, if available + if (code) { + const parts = code.split('.'); + if (parts.length >= 5) { + // We have control information in the code + const control = LZString.decompressFromBase64(Utils.fromUrlSafe(parts[4])).split('/'); + sys = parseFloat(control[0]); + eng = parseFloat(control[1]); + wep = parseFloat(control[2]); + boost = control[3] == 1 ? true : false; + fuel = parseFloat(control[4]); + cargo = parseInt(control[5]); + if (control[6]) { + const shipId = control[6]; + opponent = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots); + if (control[7] && Persist.getBuild(shipId, control[7])) { + // Ship is a particular build + const opponentCode = Persist.getBuild(shipId, control[7]); + opponent.buildFrom(opponentCode); + opponentBuild = control[7]; + if (opponentBuild) { + // Obtain opponent's sys/eng/wep pips from their code + const opponentParts = opponentCode.split('.'); + if (opponentParts.length >= 5) { + const opponentControl = LZString.decompressFromBase64(Utils.fromUrlSafe(opponentParts[4])).split('/'); + opponentSys = parseFloat(opponentControl[0]); + opponentEng = parseFloat(opponentControl[1]); + opponentWep = parseFloat(opponentControl[2]); + } + } + } else { + // Ship is a stock build + opponent.buildWith(Ships[shipId].defaults); + } + } + engagementRange = parseInt(control[8]); + } + } + + return { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange }; + } + + /** + * Triggered when pips have been updated + * @param {number} sys SYS pips + * @param {number} eng ENG pips + * @param {number} wep WEP pips + */ + _pipsUpdated(sys, eng, wep) { + this.setState({ sys, eng, wep }, () => this._updateRouteOnControlChange()); + } + + /** + * Triggered when boost has been updated + * @param {boolean} boost true if boosting + */ + _boostUpdated(boost) { + this.setState({ boost }, () => this._updateRouteOnControlChange()); + } + + /** + * Triggered when fuel has been updated + * @param {number} fuel the amount of fuel, in T + */ + _fuelUpdated(fuel) { + this.setState({ fuel }, () => this._updateRouteOnControlChange()); + } + + /** + * Triggered when cargo has been updated + * @param {number} cargo the amount of cargo, in T + */ + _cargoUpdated(cargo) { + this.setState({ cargo }, () => this._updateRouteOnControlChange()); + } + + /** + * Triggered when engagement range has been updated + * @param {number} engagementRange the engagement range, in m + */ + _engagementRangeUpdated(engagementRange) { + this.setState({ engagementRange }, () => this._updateRouteOnControlChange()); + } + + /** + * Triggered when target ship has been updated + * @param {string} opponent the opponent's ship model + * @param {string} opponentBuild the name of the opponent's build + */ + _opponentUpdated(opponent, opponentBuild) { + const opponentShip = new Ship(opponent, Ships[opponent].properties, Ships[opponent].slots); + let opponentSys = this.state.opponentSys; + let opponentEng = this.state.opponentEng; + let opponentWep = this.state.opponentWep; + if (opponentBuild && Persist.getBuild(opponent, opponentBuild)) { + // Ship is a particular build + opponentShip.buildFrom(Persist.getBuild(opponent, opponentBuild)); + // Set pips for opponent + const opponentParts = Persist.getBuild(opponent, opponentBuild).split('.'); + if (opponentParts.length >= 5) { + const opponentControl = LZString.decompressFromBase64(Utils.fromUrlSafe(opponentParts[4])).split('/'); + opponentSys = parseFloat(opponentControl[0]); + opponentEng = parseFloat(opponentControl[1]); + opponentWep = parseFloat(opponentControl[2]); + } + } else { + // Ship is a stock build + opponentShip.buildWith(Ships[opponent].defaults); + opponentSys = 2; + opponentEng = 2; + opponentWep = 2; + } + + this.setState({ opponent: opponentShip, opponentBuild, opponentSys, opponentEng, opponentWep }, () => this._updateRouteOnControlChange()); + } + + /** + * Set the control code for this outfitting page + * @param {number} fuel the fuel carried by the ship (if different from that in state) + * @param {number} cargo the cargo carried by the ship (if different from that in state) + * @returns {string} The control code + */ + _controlCode(fuel, cargo) { + const { sys, eng, wep, boost, opponent, opponentBuild, engagementRange } = this.state; + const code = `${sys}/${eng}/${wep}/${boost ? 1 : 0}/${fuel || this.state.fuel}/${cargo || this.state.cargo}/${opponent.id}/${opponentBuild ? opponentBuild : ''}/${engagementRange}`; + return code; + } + + /** + * Save the current build + */ + _saveBuild() { + const { ship, buildName, newBuildName, shipId } = this.state; + + // If this is a stock ship the code won't be set, so ensure that we have it + const code = this.state.code || ship.toString(); + + Persist.saveBuild(shipId, newBuildName, code); + this._updateRoute(shipId, newBuildName, code); + + let opponent, opponentBuild, opponentSys, opponentEng, opponentWep; + if (shipId === this.state.opponent.id && buildName === this.state.opponentBuild) { + // This is a save of our current opponent build; update it + opponentBuild = newBuildName; + opponent = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots).buildFrom(code); + opponentSys = this.state.sys; + opponentEng = this.state.eng; + opponentWep = this.state.wep; + } else { + opponentBuild = this.state.opponentBuild; + opponent = this.state.opponent; + opponentSys = this.state.opponentSys; + opponentEng = this.state.opponentEng; + opponentWep = this.state.opponentWep; + } + this.setState({ buildName: newBuildName, code, savedCode: code, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, title: this._getTitle(newBuildName) }); + } + + /** + * Rename the current build + */ + _renameBuild() { + const { code, buildName, newBuildName, shipId, ship } = this.state; + if (buildName != newBuildName && newBuildName.length) { + Persist.deleteBuild(shipId, buildName); + Persist.saveBuild(shipId, newBuildName, code); + this._updateRoute(shipId, newBuildName, code); + this.setState({ buildName: newBuildName, code, savedCode: code, opponentBuild: newBuildName }); + } + } + + /** + * Reload build from last save + */ + _reloadBuild() { + this.setState({ code: this.state.savedCode }, () => this._codeUpdated()); + } + + /** + * Reset build to Stock/Factory defaults + */ + _resetBuild() { + const { ship, shipId, buildName } = this.state; + // Rebuild ship + ship.buildWith(Ships[shipId].defaults); + // Reset controls + const code = ship.toString(); + const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code); + // Update state, and refresh the ship + this.setState({ + sys, + eng, + wep, + boost, + fuel, + cargo, + opponent, + opponentBuild, + engagementRange + }, () => this._updateRoute(shipId, buildName, code)); + } + + /** + * Delete the build + */ + _deleteBuild() { + const { shipId, buildName } = this.state; + Persist.deleteBuild(shipId, buildName); + + let opponentBuild; + if (shipId === this.state.opponent.id && buildName === this.state.opponentBuild) { + // Our current opponent has been deleted; revert to stock + opponentBuild = null; + } else { + opponentBuild = this.state.opponentBuild; + } + Router.go(outfitURL(this.state.shipId)); + + this.setState({ opponentBuild }); + } + + /** + * Serialized and show the export modal + */ + _exportBuild() { + let translate = this.context.language.translate; + let { buildName, ship } = this.state; + this.context.showModal(); + } + + /** + * Called when the code for the ship has been updated, to synchronise the rest of the data + */ + _codeUpdated() { + const { code, ship, shipId, buildName } = this.state; + + // Rebuild ship from the code + this.state.ship.buildFrom(code); + + // Obtain controls from the code + const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code); + // Update state, and refresh the route when complete + this.setState({ + sys, + eng, + wep, + boost, + fuel, + cargo, + opponent, + opponentBuild, + engagementRange + }, () => this._updateRoute(shipId, buildName, code)); + } + + /** + * Called when the ship has been updated, to set the code and then update accordingly + */ + _shipUpdated() { + let { ship, shipId, buildName, cargo, fuel } = this.state; + if (cargo > ship.cargoCapacity) { + cargo = ship.cargoCapacity; + } + if (fuel > ship.fuelCapacity) { + fuel = ship.fuelCapacity; + } + const code = this._fullCode(ship, fuel, cargo); + // Only update the state if this really has been updated + if (this.state.code != code || this.state.cargo != cargo || this.state.fuel != fuel) { + this.setState({ code, cargo, fuel }, () => this._updateRoute(shipId, buildName, code)); + } + } + + /** + * Update the current route based on build + * @param {string} shipId Ship Id + * @param {string} buildName Current build name + * @param {string} code Serialized ship 'code' + */ + _updateRoute(shipId, buildName, code) { + Router.replace(outfitURL(shipId, code, buildName)); + } + + /** + * Update state based on context changes + * @param {Object} nextProps Incoming/Next properties + * @param {Object} nextContext Incoming/Next conext + */ + componentWillReceiveProps(nextProps, nextContext) { + if (this.context.route !== nextContext.route) { // Only reinit state if the route has changed + this.setState(this._initState(nextProps, nextContext)); + } + } + + /** + * Add listeners when about to mount + */ + componentWillMount() { + document.addEventListener('keydown', this._keyDown); + } + + /** + * Remove listeners on unmount + */ + componentWillUnmount() { + document.removeEventListener('keydown', this._keyDown); + } + + /** + * Generates the short URL + */ + _genShortlink() { + this.context.showModal(); + } + + /** + * Generate Orbis link + */ + _genOrbis() { + const data = {}; + const ship = this.state.ship; + ship.coriolisId = ship.id; + data.coriolisShip = ship; + data.coriolisShip.url = window.location.href; + data.title = this.state.buildName || ship.id; + data.description = this.state.buildName || ship.id; + data.ShipName = ship.id; + data.Ship = ship.id; + console.log(data); + this.context.showModal(); + } + + /** + * Open up a window for EDDB with a shopping list of our components + */ + _eddbShoppingList() { + const ship = this.state.ship; + + const shipId = Ships[ship.id].eddbID; + // Provide unique list of non-PP module EDDB IDs + const modIds = ship.internal.concat(ship.bulkheads, ship.standard, ship.hardpoints).filter(slot => slot !== null && slot.m !== null && !slot.m.pp).map(slot => slot.m.eddbID).filter((v, i, a) => a.indexOf(v) === i); + + // Open up the relevant URL + window.open('https://eddb.io/station?s=' + shipId + '&m=' + modIds.join(',')); + } + + /** + * Generates the shopping list + */ + _genShoppingList() { + this.context.showModal(); + } + + /** + * Handle Key Down + * @param {Event} e Keyboard Event + */ + _keyDown(e) { + // .keyCode will eventually be replaced with .key + switch (e.keyCode) { + case 69: // 'e' + if (e.ctrlKey || e.metaKey) { // CTRL/CMD + e + e.preventDefault(); + this._exportBuild(); + } + break; + } + } + + /** + * Render the Page + * @return {React.Component} The page contents + */ + renderPage() { + let state = this.state, + { language, termtip, tooltip, sizeRatio, onWindowResize } = this.context, + { translate, units, formats } = language, + { ship, code, savedCode, buildName, newBuildName, sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = state, + hide = tooltip.bind(null, null), + menu = this.props.currentMenu, + shipUpdated = this._shipUpdated, + canSave = (newBuildName || buildName) && code !== savedCode, + canRename = buildName && newBuildName && buildName != newBuildName, + canReload = savedCode && canSave; + + // Code can be blank for a default loadout. Prefix it with the ship name to ensure that changes in default ships is picked up + code = ship.name + (code || ''); + + // Markers are used to propagate state changes without requiring a deep comparison of the ship, as that takes a long time + const _sStr = ship.getStandardString(); + const _iStr = ship.getInternalString(); + const _hStr = ship.getHardpointsString(); + const _pStr = `${ship.getPowerEnabledString()}${ship.getPowerPrioritiesString()}`; + const _mStr = ship.getModificationsString(); + + const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`; + const internalSlotMarker = `${ship.name}${_iStr}${_pStr}${_mStr}`; + const hardpointsSlotMarker = `${ship.name}${_hStr}${_pStr}${_mStr}`; + const boostMarker = `${ship.canBoost(cargo, fuel)}`; + const shipSummaryMarker = `${ship.name}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`; + + const requirements = Ships[ship.id].requirements; + let requirementElements = []; + /** + * Render the requirements for a ship / etc + * @param {string} className Class names + * @param {string} textKey The key for translating + * @param {String} tooltipTextKey Tooltip key + */ + function renderRequirement(className, textKey, tooltipTextKey) { + if (textKey.startsWith('empire') || textKey.startsWith('federation')) { + requirementElements.push(); + } else { + requirementElements.push(
    {translate(textKey)}
    ); + } + } + + if (requirements) { + requirements.federationRank && renderRequirement('federation', 'federation rank ' + requirements.federationRank, 'federation rank required'); + requirements.empireRank && renderRequirement('empire', 'empire rank ' + requirements.empireRank, 'empire rank required'); + requirements.horizons && renderRequirement('horizons', 'horizons', 'horizons required'); + requirements.horizonsEarlyAdoption && renderRequirement('horizons', 'horizons early adoption', 'horizons early adoption required'); + } + + return ( +
    +
    +

    {ship.name}

    +
    {requirementElements}
    +
    + + + + + + + + + + + +
    +
    + + {/* Main tables */} + + + + + + + {/* Control of ship and opponent */} +
    +
    +

    {translate('ship control')}

    +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    + { ship.cargoCapacity > 0 ? : null } +
    +
    +
    +

    {translate('opponent')}

    +
    +
    + +
    +
    +
    + +
    + + {/* Tabbed subpages */} + +
    + ); + } +} diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index 81a0187c..350d0374 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -741,16 +741,6 @@ export default class Module { * @return {string} the shot speed for this module */ getShotSpeed() { - if (this.blueprint && (this.blueprint.name === 'Focused' || this.blueprint.name === 'Long range')) { - // If the modification is focused or long range then the shot speed - // uses the range modifier - const rangemod = this.getModValue('range') / 10000; - let result = this['shotspeed']; - if (!result) { - return null; - } - return result * (1 + rangemod); - } return this._getModifiedValue('shotspeed'); } diff --git a/src/app/stores/Persist.js b/src/app/stores/Persist.js index 2d00628e..e2724dbd 100644 --- a/src/app/stores/Persist.js +++ b/src/app/stores/Persist.js @@ -15,6 +15,7 @@ const LS_KEY_SIZE_RATIO = 'sizeRatio'; const LS_KEY_TOOLTIPS = 'tooltips'; const LS_KEY_MODULE_RESISTANCES = 'moduleResistances'; const LS_KEY_ROLLS = 'matsPerGrade'; +const LS_KEY_ORBIS = 'orbis'; let LS; @@ -95,6 +96,7 @@ export class Persist extends EventEmitter { let buildJson = _get(LS_KEY_BUILDS); let comparisonJson = _get(LS_KEY_COMPARISONS); + this.orbisCreds = _get(LS_KEY_ORBIS) || { email: '', password: '' }; this.onStorageChange = this.onStorageChange.bind(this); this.langCode = _getString(LS_KEY_LANG) || 'en'; this.insurance = insurance && Insurance[insurance.toLowerCase()] !== undefined ? insurance : 'standard'; @@ -168,6 +170,10 @@ export class Persist extends EventEmitter { this.matsPerGrade = JSON.parse(newValue); this.emit('matsPerGrade', this.matsPerGrade); break; + case LS_KEY_ORBIS: + this.orbisCreds = JSON.parse(newValue); + this.emit('orbis', this.orbisCreds); + break; } } catch (e) { // On JSON.Parse Error - don't sync or do anything @@ -193,6 +199,24 @@ export class Persist extends EventEmitter { this.emit('language', langCode); } + /** + * Get the current orbis.zone credentials + * @return {String} language code + */ + getOrbisCreds() { + return this.orbisCreds; + }; + + /** + * Update and save the orbis.zone credentials + * @param {Object} creds object with username and password properties. + */ + setOrbisCreds(creds) { + this.langCode = creds; + _put(LS_KEY_ORBIS, creds); + this.emit('orbis', creds); + } + /** * Show tooltips setting * @param {boolean} show Optional - update setting diff --git a/src/app/utils/CompanionApiUtils.js b/src/app/utils/CompanionApiUtils.js index 325f32b9..23fd557f 100644 --- a/src/app/utils/CompanionApiUtils.js +++ b/src/app/utils/CompanionApiUtils.js @@ -6,7 +6,7 @@ import { getBlueprint } from '../utils/BlueprintFunctions'; import * as ModuleUtils from '../shipyard/ModuleUtils'; // mapping from fd's ship model names to coriolis' -const SHIP_FD_NAME_TO_CORIOLIS_NAME = { +export const SHIP_FD_NAME_TO_CORIOLIS_NAME = { 'Adder': 'adder', 'Anaconda': 'anaconda', 'Asp': 'asp', diff --git a/src/app/utils/JournalUtils.js b/src/app/utils/JournalUtils.js index e9094544..e3daaf3c 100644 --- a/src/app/utils/JournalUtils.js +++ b/src/app/utils/JournalUtils.js @@ -281,6 +281,9 @@ function _addModifications(module, modifiers, blueprint, grade, specialModificat if (modifiers[i].Label.search('Resistance') >= 0) { value = (modifiers[i].Value * 100) - (modifiers[i].OriginalValue * 100); } + if (modifiers[i].Label.search('ShieldMultiplier') >= 0) { + value = ((100 + modifiers[i].Value) / (100 + modifiers[i].OriginalValue) * 100 - 100) * 100; + } // Carry out the required changes for (const action in modifierActions) { diff --git a/src/app/utils/ShortenUrl.js b/src/app/utils/ShortenUrl.js index 1a0b57ce..f2c31a63 100644 --- a/src/app/utils/ShortenUrl.js +++ b/src/app/utils/ShortenUrl.js @@ -1,14 +1,23 @@ import request from 'superagent'; +let agent; +try { + agent = request.agent(); // apparently this crashes somehow +} catch (e) { + console.error(e); +} +if (!agent) { + agent = request; +} /** * Shorten a URL * @param {string} url The URL to shorten * @param {function} success Success callback * @param {function} error Failure/Error callback - */ + */ export default function shorternUrl(url, success, error) { - shortenUrlOrbis(url, success, error); + orbisShorten(url, success, error); } const SHORTEN_API_GOOGLE = 'https://www.googleapis.com/urlshortener/v1/url?key='; @@ -72,7 +81,7 @@ const SHORTEN_API_ORBIS = 'https://s.orbis.zone/api.php'; * @param {function} success Success callback * @param {function} error Failure/Error callback */ -function shortenUrlOrbis(url, success, error) { +function orbisShorten(url, success, error) { if (window.navigator.onLine) { try { request.post(SHORTEN_API_ORBIS) @@ -95,3 +104,37 @@ function shortenUrlOrbis(url, success, error) { error('Not Online'); } } + +const API_ORBIS = 'https://orbis.zone/api/builds/add'; +/** + * Upload to Orbis + * @param {object} ship The URL to shorten + * @param {object} creds Orbis credentials + * @return {Promise} Either a URL or error message. + */ +export function orbisUpload(ship, creds) { + return new Promise(async (resolve, reject) => { + if (window.navigator.onLine) { + try { + agent + .post(API_ORBIS) + .withCredentials() + .redirects(0) + .set('Content-Type', 'application/json') + .send(ship) + .end(function(err, response) { + if (err) { + reject('Bad Request'); + } else { + resolve(response.body.link); + } + }); + } catch (e) { + console.log(e); + reject(e.message ? e.message : e); + } + } else { + reject('Not Online'); + } + }); +} diff --git a/src/index.ejs b/src/index.ejs index c89a866f..32bccb3d 100644 --- a/src/index.ejs +++ b/src/index.ejs @@ -26,7 +26,8 @@ <% if (htmlWebpackPlugin.options.uaTracking) { %> <% } %> - +