diff --git a/src/app/Coriolis.jsx b/src/app/Coriolis.jsx index d5e36855..d8862f1c 100644 --- a/src/app/Coriolis.jsx +++ b/src/app/Coriolis.jsx @@ -16,7 +16,7 @@ import ModalPermalink from './components/ModalPermalink'; import AboutPage from './pages/AboutPage'; import NotFoundPage from './pages/NotFoundPage'; import OutfittingPage from './pages/OutfittingPage'; -import ComparisonPage from './pages/ComparisonPage'; +// import ComparisonPage from './pages/ComparisonPage'; import ShipyardPage from './pages/ShipyardPage'; import ErrorDetails from './pages/ErrorDetails'; @@ -76,9 +76,9 @@ export default class Coriolis extends React.Component { Router('/outfit/?', (r) => this._setPage(OutfittingPage, r)); Router('/outfit/:ship/?', (r) => this._setPage(OutfittingPage, r)); Router('/outfit/:ship/:code?', (r) => this._setPage(OutfittingPage, r)); - Router('/compare/:name?', (r) => this._setPage(ComparisonPage, r)); - Router('/comparison?', (r) => this._setPage(ComparisonPage, r)); - Router('/comparison/:code', (r) => this._setPage(ComparisonPage, r)); + // Router('/compare/:name?', (r) => this._setPage(ComparisonPage, r)); + // Router('/comparison?', (r) => this._setPage(ComparisonPage, r)); + // Router('/comparison/:code', (r) => this._setPage(ComparisonPage, r)); Router('/about', (r) => this._setPage(AboutPage, r)); Router('*', (r) => this._setPage(null, r)); } diff --git a/src/app/components/Defence.jsx b/src/app/components/Defence.jsx index a74018e3..89fe242c 100644 --- a/src/app/components/Defence.jsx +++ b/src/app/components/Defence.jsx @@ -1,7 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import TranslatedComponent from './TranslatedComponent'; -import * as Calc from '../shipyard/Calculations'; import PieChart from './PieChart'; import VerticalBarChart from './VerticalBarChart'; import autoBind from 'auto-bind'; diff --git a/src/app/components/FSDProfile.jsx b/src/app/components/FSDProfile.jsx index 90761382..f6e1f539 100644 --- a/src/app/components/FSDProfile.jsx +++ b/src/app/components/FSDProfile.jsx @@ -2,7 +2,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import TranslatedComponent from './TranslatedComponent'; import LineChart from '../components/LineChart'; -import * as Calc from '../shipyard/Calculations'; import { calculateJumpRange } from 'ed-forge/lib/stats/JumpRangeProfile'; import { ShipProps } from 'ed-forge'; const { LADEN_MASS } = ShipProps; @@ -18,18 +17,6 @@ export default class FSDProfile extends TranslatedComponent { fuel: PropTypes.number.isRequired, }; - /** - * Calculate the maximum range for this ship across its applicable mass - * @param {Object} ship The ship - * @param {Object} fuel The fuel on the ship - * @param {Object} mass The mass at which to calculate the maximum range - * @return {number} The maximum range - */ - _calcMaxRange(ship, fuel, mass) { - // Obtain the maximum range - return Calc.jumpRange(mass, ship.standard[2].m, Math.min(fuel, ship.standard[2].m.getMaxFuelPerJump()), ship); - } - /** * Render FSD profile * @return {React.Component} contents diff --git a/src/app/components/Header.jsx b/src/app/components/Header.jsx index a79a6e1c..e8cf35e9 100644 --- a/src/app/components/Header.jsx +++ b/src/app/components/Header.jsx @@ -8,7 +8,6 @@ import cn from 'classnames'; import { Cogs, CoriolisLogo, Hammer, Help, Rocket, StatsBars } from './SvgIcons'; 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'; diff --git a/src/app/components/InternalSlotSection.jsx b/src/app/components/InternalSlotSection.jsx index 89bdc6cc..1a305540 100644 --- a/src/app/components/InternalSlotSection.jsx +++ b/src/app/components/InternalSlotSection.jsx @@ -1,9 +1,7 @@ import React from 'react'; import SlotSection from './SlotSection'; import Slot from './Slot'; -import * as ModuleUtils from '../shipyard/ModuleUtils'; import { stopCtxPropagation } from '../utils/UtilityFunctions'; -import { canMount } from '../utils/SlotFunctions'; import autoBind from 'auto-bind'; /** diff --git a/src/app/components/ModalImport.jsx b/src/app/components/ModalImport.jsx index b1a789de..1702dc92 100644 --- a/src/app/components/ModalImport.jsx +++ b/src/app/components/ModalImport.jsx @@ -2,84 +2,16 @@ import React from 'react'; import PropTypes from 'prop-types'; import cn from 'classnames'; import TranslatedComponent from './TranslatedComponent'; -import Router from '../Router'; import Persist from '../stores/Persist'; -import { Ships } from 'coriolis-data/dist'; -import Ship from '../shipyard/Ship'; -import { ModuleNameToGroup, Insurance } from '../shipyard/Constants'; -import * as ModuleUtils from '../shipyard/ModuleUtils'; -import { fromDetailedBuild } from '../shipyard/Serializer'; -import { Download } from './SvgIcons'; -import { outfitURL } from '../utils/UrlGenerators'; -import * as CompanionApiUtils from '../utils/CompanionApiUtils'; +import autoBind from 'auto-bind'; +import { isArray } from 'lodash'; +import { Ship } from 'ed-forge'; -const zlib = require('pako'); - -const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n'); -const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)'); -const mountMap = { 'H': 4, 'L': 3, 'M': 2, 'S': 1, 'U': 0 }; -const standardMap = { 'RB': 0, 'TM': 1, 'FH': 2, 'EC': 3, 'PC': 4, 'SS': 5, 'FS': 6 }; -const bhMap = { 'lightweight alloy': 0, 'reinforced alloy': 1, 'military grade composite': 2, 'mirrored surface composite': 3, 'reactive surface composite': 4 }; - -/** - * Check is slot is empty - * @param {Object} slot Slot model - * @return {Boolean} True if empty - */ -function isEmptySlot(slot) { - return slot.maxClass == this && slot.m === null; -} - -/** - * Determine if a build is valid - * @param {string} shipId Ship ID - * @param {string} code Serialzied ship build 'code' - * @param {string} name Build name - * @throws {string} If build is not valid - */ -function validateBuild(shipId, code, name) { - let shipData = Ships[shipId]; - - if (!shipData) { - throw '"' + shipId + '" is not a valid Ship Id!'; - } - if (typeof name != 'string' || name.length == 0) { - throw shipData.properties.name + ' build "' + name + '" must be a string at least 1 character long!'; - } - if (typeof code != 'string' || code.length < 10) { - throw shipData.properties.name + ' build "' + name + '" is not valid!'; - } - try { - let ship = new Ship(shipId, shipData.properties, shipData.slots); - ship.buildFrom(code); - } catch (e) { - throw shipData.properties.name + ' build "' + name + '" is not valid!'; - } -} - -/** - * Convert a ship-loadout JSON object to a Coriolis build - * @param {Object} detailedBuild ship-loadout - * @return {Object} Coriolis build - */ -function detailedJsonToBuild(detailedBuild) { - let ship; - if (!detailedBuild.name) { - throw 'Build Name missing!'; - } - - if (!detailedBuild.name.trim()) { - throw 'Build Name must be a string at least 1 character long!'; - } - - try { - ship = fromDetailedBuild(detailedBuild); - } catch (e) { - throw detailedBuild.ship + ' Build "' + detailedBuild.name + '": Invalid data'; - } - - return { shipId: ship.id, name: detailedBuild.name, code: ship.toString() }; -} +const STATE = { + READY: 0, + PARSED: 1, + ERROR: 2, +}; /** * Import Modal @@ -95,228 +27,12 @@ export default class ModalImport extends TranslatedComponent { */ constructor(props) { super(props); + autoBind(this); this.state = { - builds: props.builds, - canEdit: !props.builds, - loadoutEvent: null, - comparisons: null, - shipDiscount: null, - moduleDiscount: null, - errorMsg: null, - importString: null, - importValid: false, - insurance: null + status: STATE.READY, + builds: props.builds || [], }; - - this._process = this._process.bind(this); - this._import = this._import.bind(this); - this._importBackup = this._importBackup.bind(this); - this._importLoadout = this._importLoadout.bind(this); - this._importDetailedArray = this._importDetailedArray.bind(this); - this._importTextBuild = this._importTextBuild.bind(this); - this._importCompanionApiBuild = this._importCompanionApiBuild.bind(this); - this._validateImport = this._validateImport.bind(this); - } - - /** - * Import a Loadout event from Elite: Dangerous journal files - * @param {Object} data Loadout event - * @throws {string} If import fails - */ - _importLoadout(data) { - if (data && data.Ship && data.Modules) { - const deflated = zlib.deflate(JSON.stringify(data), { to: 'string' }); - let compressed = btoa(deflated); - this.setState({ loadoutEvent: compressed }); - } else { - throw 'Loadout event must contain Ship and Modules'; - } - } - - /** - * Import a Coriolis backup - * @param {Object} importData Backup Data - * @throws {string} If import fails - */ - _importBackup(importData) { - if (importData.builds && typeof importData.builds == 'object') { - for (let shipId in importData.builds) { - for (let buildName in importData.builds[shipId]) { - try { - validateBuild(shipId, importData.builds[shipId][buildName], buildName); - } catch (err) { - delete importData.builds[shipId][buildName]; - } - } - } - this.setState({ builds: importData.builds }); - } else { - throw 'builds must be an object!'; - } - if (importData.comparisons) { - for (let compName in importData.comparisons) { - let comparison = importData.comparisons[compName]; - for (let i = 0, l = comparison.builds.length; i < l; i++) { - let build = comparison.builds[i]; - if (!importData.builds[build.shipId] || !importData.builds[build.shipId][build.buildName]) { - throw build.shipId + ' build "' + build.buildName + '" data is missing!'; - } - } - } - this.setState({ comparisons: importData.comparisons }); - } - // Check for old/deprecated discounts - if (importData.discounts instanceof Array && importData.discounts.length == 2) { - this.setState({ shipDiscount: importData.discounts[0], moduleDiscount: importData.discounts[1] }); - } - // Check for ship discount - if (!isNaN(importData.shipDiscount)) { - this.setState({ shipDiscount: importData.shipDiscount * 1 }); - } - // Check for module discount - if (!isNaN(importData.moduleDiscount)) { - this.setState({ moduleDiscount: importData.moduleDiscount * 1 }); - } - - if (typeof importData.insurance == 'string') { - let insurance = importData.insurance.toLowerCase(); - - if (Insurance[insurance] !== undefined) { - this.setState({ insurance }); - } else { - throw 'Invalid insurance type: ' + insurance; - } - } - } - - /** - * Import an array of ship-loadout objects / builds - * @param {Array} importArr Array of ship-loadout JSON Schema builds - */ - _importDetailedArray(importArr) { - let builds = {}; - for (let i = 0, l = importArr.length; i < l; i++) { - let build = detailedJsonToBuild(importArr[i]); - if (!builds[build.shipId]) { - builds[build.shipId] = {}; - } - builds[build.shipId][build.name] = build.code; - } - this.setState({ builds }); - } - - /** - * Import a build direct from the companion API - * @param {string} build JSON from the companion API information - * @throws {string} if parse/import fails - */ - _importCompanionApiBuild(build) { - const shipModel = CompanionApiUtils.shipModelFromJson(build); - const ship = CompanionApiUtils.shipFromJson(build); - - let builds = {}; - builds[shipModel] = {}; - builds[shipModel]['Imported ' + Ships[shipModel].properties.name] = ship.toString(); - this.setState({ builds, singleBuild: true }); - } - - /** - * Import a text build from ED Shipyard - * @param {string} buildStr Build string - * @throws {string} If parse / import fails - */ - _importTextBuild(buildStr) { - let buildName = textBuildRegex.exec(buildStr)[1].trim(); - let shipName = buildName.toLowerCase(); - let shipId = null; - - for (let sId in Ships) { - if (Ships[sId].properties.name.toLowerCase() == shipName) { - shipId = sId; - break; - } - } - - if (!shipId) { - throw 'No such ship found: "' + buildName + '"'; - } - - let lines = buildStr.split('\n'); - let ship = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots); - ship.buildWith(null); - - for (let i = 1; i < lines.length; i++) { - let line = lines[i].trim(); - - if (!line) { continue; } - if (line.substring(0, 3) == '---') { break; } - - let parts = lineRegex.exec(line); - - if (!parts) { throw 'Error parsing: "' + line + '"'; } - - let typeSize = parts[1]; - let cl = parts[2]; - let rating = parts[3]; - let mount = parts[4]; - let missile = parts[5]; - let name = parts[6].trim(); - let slot, group; - - if (isNaN(typeSize)) { // Standard or Hardpoint - if (typeSize.length == 1) { // Hardpoint - let slotClass = mountMap[typeSize]; - - if (cl > slotClass) { throw cl + rating + ' ' + name + ' exceeds slot size: "' + line + '"'; } - - slot = ship.hardpoints.find(isEmptySlot, slotClass); - - if (!slot) { throw 'No hardpoint slot available for: "' + line + '"'; } - - group = ModuleNameToGroup[name.toLowerCase()]; - - let hp = ModuleUtils.findHardpoint(group, cl, rating, group ? null : name, mount, missile); - - if (!hp) { throw 'Unknown component: "' + line + '"'; } - - ship.use(slot, hp, true); - } else if (typeSize == 'BH') { - let bhId = bhMap[name.toLowerCase()]; - - if (bhId === undefined) { throw 'Unknown bulkhead: "' + line + '"'; } - - ship.useBulkhead(bhId, true); - } else if (standardMap[typeSize] != undefined) { - let standardIndex = standardMap[typeSize]; - - if (ship.standard[standardIndex].maxClass < cl) { throw name + ' exceeds max class for the ' + ship.name; } - - ship.use(ship.standard[standardIndex], ModuleUtils.standard(standardIndex, cl + rating), true); - } else { - throw 'Unknown component: "' + line + '"'; - } - } else { - if (cl > typeSize) { throw cl + rating + ' ' + name + ' exceeds slot size: "' + line + '"'; } - - slot = ship.internal.find(isEmptySlot, typeSize); - - if (!slot) { throw 'No internal slot available for: "' + line + '"'; } - - group = ModuleNameToGroup[name.toLowerCase()]; - - let intComp = ModuleUtils.findInternal(group, cl, rating, group ? null : name); - - if (!intComp) { throw 'Unknown component: "' + line + '"'; } - - ship.use(slot, intComp); - } - } - - let builds = {}; - builds[shipId] = {}; - builds[shipId]['Imported ' + buildName] = ship.toString(); - this.setState({ builds, singleBuild: true }); } /** @@ -324,164 +40,50 @@ export default class ModalImport extends TranslatedComponent { * @param {SyntheticEvent} event Event * @throws {string} If validation fails */ - _validateImport(event) { - let importData = null; - let importString = event.target.value.trim(); - this.setState({ - builds: null, - comparisons: null, - shipDiscount: null, - moduleDiscount: null, - errorMsg: null, - importValid: false, - insurance: null, - singleBuild: false, - importString, - }); - - if (!importString) { - return; - } - + _parse(event) { + const importString = event.target.value.trim(); try { - if (textBuildRegex.test(importString)) { // E:D Shipyard build text - this._importTextBuild(importString); - } else { // JSON Build data - importData = JSON.parse(importString); - - if (!importData || typeof importData != 'object') { - throw 'Must be an object or array!'; - } - - if (importData.modules != null && importData.modules.Armour != null) { // Only the companion API has this information - this._importCompanionApiBuild(importData); // Single sihp definition - } else if (importData.ship != null && importData.ship.modules != null && importData.ship.modules.Armour != null) { // Only the companion API has this information - this._importCompanionApiBuild(importData.ship); // Complete API dump - } else if (importData instanceof Array) { // Must be detailed export json - this._importDetailedArray(importData); - } else if (importData.ship && typeof importData.name !== undefined) { // Using JSON from a single ship build export - this._importDetailedArray([importData]); // Convert to array with singleobject - this.setState({ singleBuild: true }); - } else if (importData.Modules != null && importData.Modules[0] != null) { - this._importLoadout(importData); - } else { // Using Backup JSON - this._importBackup(importData); - } + let data = JSON.parse(importString); + if (!isArray(data)) { + data = [data]; } - } catch (e) { - console.log(e); - this.setState({ errorMsg: (typeof e == 'string') ? e : 'Cannot Parse the data!' }); - return; - } - this.setState({ importValid: true }); - }; + const ships = data.map((item) => { + try { + return new Ship(item.data ? item.data : item); + } catch (err) { + return err; + } + }); + this.setState({ ships, status: STATE.PARSED }); + } catch (err) { + this.setState({ err, status: STATE.ERROR }); + } + } /** * Process imported data */ _process() { - let builds = null, comparisons = null; - - if (this.state.loadoutEvent) { - return Router.go(`/import?data=${this.state.loadoutEvent}`); - } - - // If only importing a single build go straight to the outfitting page - if (this.state.singleBuild) { - builds = this.state.builds; - let shipId = Object.keys(builds)[0]; - let name = Object.keys(builds[shipId])[0]; - Router.go(outfitURL(shipId, builds[shipId][name], name)); - return; - } - - - if (this.state.builds) { - builds = {}; // Create new builds object such that orginal name retained, but can be renamed - for (let shipId in this.state.builds) { - let shipbuilds = this.state.builds[shipId]; - builds[shipId] = {}; - for (let buildName in shipbuilds) { - builds[shipId][buildName] = { - code: shipbuilds[buildName], - useName: buildName - }; - } + for (const build of this.state.builds) { + if (!build instanceof Error) { + Persist.saveBuild(build.Ship, build.CoriolisBuildName || build.ShipName, build.compress()); } } - - if (this.state.comparisons) { - comparisons = {}; - for (let name in this.state.comparisons) { - comparisons[name] = Object.assign({ useName: name }, this.state.comparisons[name]); - } - } - - this.setState({ processed: true, builds, comparisons }); - }; - - /** - * Import parsed, processed data and save - */ - _import() { - let state = this.state; - if (state.builds) { - let builds = state.builds; - for (let shipId in builds) { - for (let buildName in builds[shipId]) { - let build = builds[shipId][buildName]; - let name = build.useName.trim(); - if (name) { - Persist.saveBuild(shipId, name, build.code); - } - } - } - } - - if (state.comparisons) { - let comparisons = state.comparisons; - for (let comp in comparisons) { - let comparison = comparisons[comp]; - let useName = comparison.useName.trim(); - if (useName) { - Persist.saveComparison(useName, comparison.builds, comparison.facets); - } - } - } - - if (state.shipDiscount !== undefined) { - Persist.setShipDiscount(state.shipDiscount); - } - if (state.moduleDiscount !== undefined) { - Persist.setModuleDiscount(state.moduleDiscount); - } - - if (state.insurance) { - Persist.setInsurance(state.insurance); - } - - this.context.hideModal(); - }; + this.setState({ builds: [], status: STATE.READY }); + } /** * Capture build name changes - * @param {Object} item Build/Comparison import object - * @param {SyntheticEvent} e Event + * @param {Object} index Build/Comparison import object + * @param {SyntheticEvent} event Event */ - _changeName(item, e) { - item.useName = e.target.value; - this.forceUpdate(); + _changeName(index, event) { + const { builds } = this.state; + builds[index].CoriolisBuildName = event.target.value.trim(); + this.setState({ builds }); } - /** - * If imported data is already provided process immediately on mount - */ - componentWillMount() { - if (this.props.builds) { - this._process(); - } - } /** * If textarea is shown focus on mount */ @@ -496,100 +98,54 @@ export default class ModalImport extends TranslatedComponent { * @return {React.Component} Modal contents */ render() { - let translate = this.context.language.translate; - let state = this.state; - let importStage; + const { translate } = this.context.language; + const { status, builds, err } = this.state; - if (!state.processed) { - importStage = ( -
-