From b19a36e4e8e6cf1c53e79e9000339181aaf066b2 Mon Sep 17 00:00:00 2001 From: Alex Williams Date: Thu, 20 Jun 2024 23:55:46 +0100 Subject: [PATCH 1/2] Merge Coriolis beta to live - beta.coriolis.io content to deploy on coriolis.io (#14) * Update pt.json - Brazilian Portuguese translations (#752) * Update pt.json Update Brazilian Portuguese translations: - Updated Modules - Engineering & Experimental Effect - Corrections * Update Portuguese Brazilian Fixed Tab/Spaces indentation * Updated PT-BR translation with Planetary Approach Suite * Adds valid module checking to all types of modules on import * Adds the Advanced MC's, AX MC's, AX MR's and Nanite Torpedo * Changes as per comments on the PR * Fix changed files issue (#3) * Copied de.js contents to new file de-fix.js * Copied de.js contents back from de-fix.js * Copied contents of ko.js to ko-fix.js * Copied ko.js contents back from ko-fix.js * Copied contents from BlueprintFunctions.js to BlueprintFunctions-fix.js * Copied contents back from BlueprintFunctions-fix.js to BlueprintFunctions.js * Copied contents of LineChart.jsx to LineChart-fix.jsx * Copied contents back from LineChart-fix.jsx to LineChart.jsx * Copied contents of PieChart.jsx to PieChart-fix.jsx * Copied contents back from PieChart-fix.jsx to PieChart.jsx * Copied contents from Slider.jsx to Slider-fix.jsx * Copied contents back from Slider-fix.jsx to Slider.jsx * Copied contents from VerticalBarChart.jsx to VerticalBarChart-fix.jsx * Copied contents back from VerticalBarChart-fix.jsx to VerticalBarChart.jsx * Deleting 'fix' files * Adding workflow for autodeploy * Improving workflow * Changed deployment ordering * Changing to clone single branch for deployment, not the whole repo * Adds the Advanced MC's, AX MC's, AX MR's and Nanite Torpedo (#4) * Issue 754 imports need to be more graceful (#5) * Adds valid module checking to all types of modules on import * Changes as per comments on the PR * Added 'special' field to certain modules to allow for clearer appearance in search results that they are the special type of module. Updated English descriptions of Advanced Modules and Special Modules * Update PT-BR translations Added translated strings for coriolis-data PRs 106 & 107 * Fixed 'Missing Module' category showing up in Optional Selection drop-down and fixed 'Missing Power Plant', 'Missing Power Distributor' and 'Missing Frameshift Drive' showing up in the Selection drop-downs for those module slots. * Fixing bug introduced by the previous PR for ISSUE_764. The previous fix introduced a bug which caused Armour Selection to error, due to Armour modules being completely different to other modules of any other type * Modified export to EDOMH/EDEngineer page to be less 'bodged', allow EDOMH button to be clickable without checking for EDEngineer API (If they have EDOMH, they probably don't have EDEngineer...) and added a workaround for Coriolis sending bogus data for bulkheads. * Fixed autodeploy to do latest coriolis-data dist. Fixed sendToEDOMH function to only send the blueprint at the selected grade, not each grade up to that grade. * Issue 703 edomh integration (#7) * Adds valid module checking to all types of modules on import * Adds the Advanced MC's, AX MC's, AX MR's and Nanite Torpedo * Changes as per comments on the PR * Modified export to EDOMH/EDEngineer page to be less 'bodged', allow EDOMH button to be clickable without checking for EDEngineer API (If they have EDOMH, they probably don't have EDEngineer...) and added a workaround for Coriolis sending bogus data for bulkheads. --------- Co-authored-by: David Sangrey Co-authored-by: Felix Linker * Fixed miscalculation of mats and got rid of unhelpful 'rolls' table, as the mats are calculated for the whole build and some blueprints may not be all the way up to g5. * Removed console.log lines which were only needed for testing. * Adding in buildname to EDOMH Export * Issue 703 edomh integration (#8) * Adds valid module checking to all types of modules on import * Adds the Advanced MC's, AX MC's, AX MR's and Nanite Torpedo * Changes as per comments on the PR * Modified export to EDOMH/EDEngineer page to be less 'bodged', allow EDOMH button to be clickable without checking for EDEngineer API (If they have EDOMH, they probably don't have EDEngineer...) and added a workaround for Coriolis sending bogus data for bulkheads. * Fixed autodeploy to do latest coriolis-data dist. Fixed sendToEDOMH function to only send the blueprint at the selected grade, not each grade up to that grade. --------- Co-authored-by: David Sangrey Co-authored-by: Felix Linker * Issue 703 edomh integration (#9) * Adds valid module checking to all types of modules on import * Adds the Advanced MC's, AX MC's, AX MR's and Nanite Torpedo * Changes as per comments on the PR * Modified export to EDOMH/EDEngineer page to be less 'bodged', allow EDOMH button to be clickable without checking for EDEngineer API (If they have EDOMH, they probably don't have EDEngineer...) and added a workaround for Coriolis sending bogus data for bulkheads. * Fixed autodeploy to do latest coriolis-data dist. Fixed sendToEDOMH function to only send the blueprint at the selected grade, not each grade up to that grade. * Fixed miscalculation of mats and got rid of unhelpful 'rolls' table, as the mats are calculated for the whole build and some blueprints may not be all the way up to g5. --------- Co-authored-by: David Sangrey Co-authored-by: Felix Linker * Issue 703 edomh integration (#10) * Adds valid module checking to all types of modules on import * Adds the Advanced MC's, AX MC's, AX MR's and Nanite Torpedo * Changes as per comments on the PR * Modified export to EDOMH/EDEngineer page to be less 'bodged', allow EDOMH button to be clickable without checking for EDEngineer API (If they have EDOMH, they probably don't have EDEngineer...) and added a workaround for Coriolis sending bogus data for bulkheads. * Fixed autodeploy to do latest coriolis-data dist. Fixed sendToEDOMH function to only send the blueprint at the selected grade, not each grade up to that grade. * Fixed miscalculation of mats and got rid of unhelpful 'rolls' table, as the mats are calculated for the whole build and some blueprints may not be all the way up to g5. * Removed console.log lines which were only needed for testing. --------- Co-authored-by: David Sangrey Co-authored-by: Felix Linker * Issue 764 unknown modules are selectable (#11) * Adds valid module checking to all types of modules on import * Adds the Advanced MC's, AX MC's, AX MR's and Nanite Torpedo * Changes as per comments on the PR * Fixed 'Missing Module' category showing up in Optional Selection drop-down and fixed 'Missing Power Plant', 'Missing Power Distributor' and 'Missing Frameshift Drive' showing up in the Selection drop-downs for those module slots. --------- Co-authored-by: David Sangrey Co-authored-by: Felix Linker * Adding tag to manual dispatch of workflow * Adding fix for broken Armour Module Selection * Fixed issue with special blueprint item not being correctly jsonified for export to EDOMH * Removing Autodeploy from this branch, it was merged in by github * Removing debugging console.log entries that are no longer needed for EDOMH fix * Adding autodeploy for new 'beta' branch * Fixing directory for beta deployment * Updating beta autodeploy with nvm info --------- Co-authored-by: leonardofelin <33718368+leonardofelin@users.noreply.github.com> Co-authored-by: David Sangrey Co-authored-by: Felix Linker --- .github/workflows/autodeploy.yml | 3 +- src/app/components/AvailableModulesMenu.jsx | 44 +- src/app/components/HardpointSlot.jsx | 1 + src/app/components/InternalSlot.jsx | 1 + src/app/components/LineChart.jsx | 562 ++++++------- src/app/components/ModalShoppingList.jsx | 126 ++- src/app/components/PieChart.jsx | 184 ++--- src/app/components/Slider.jsx | 772 ++++++++--------- src/app/components/Slot.jsx | 15 +- src/app/components/StandardSlot.jsx | 10 +- src/app/components/VerticalBarChart.jsx | 160 ++-- src/app/i18n/de.js | 32 +- src/app/i18n/en.json | 16 +- src/app/i18n/ko.js | 32 +- src/app/i18n/pt.json | 349 +++++--- src/app/pages/ErrorDetails.jsx | 3 + src/app/pages/OutfittingPage.jsx | 4 +- src/app/shipyard/Module.js | 9 + src/app/utils/BlueprintFunctions.js | 870 ++++++++++---------- src/app/utils/JournalUtils.js | 123 ++- src/less/app.less | 2 +- src/less/modal.less | 9 + 22 files changed, 1853 insertions(+), 1474 deletions(-) diff --git a/.github/workflows/autodeploy.yml b/.github/workflows/autodeploy.yml index 40c5bbd8..98baf5db 100644 --- a/.github/workflows/autodeploy.yml +++ b/.github/workflows/autodeploy.yml @@ -1,5 +1,6 @@ # This is a basic deployment workflow triggered by pushes to the alpha branch. + name: Auto-Deploy 'live' Branch to coliolis.io # Controls when the action will run. Workflow runs when the alpha branch receives a push event @@ -27,4 +28,4 @@ jobs: cd ../coriolis npm install npm run build - sudo -u www-data cp -r ./build/* /var/www/coriolis.io/ \ No newline at end of file + sudo -u www-data cp -r ./build/* /var/www/coriolis.io/ diff --git a/src/app/components/AvailableModulesMenu.jsx b/src/app/components/AvailableModulesMenu.jsx index de4a6e17..87f188b6 100644 --- a/src/app/components/AvailableModulesMenu.jsx +++ b/src/app/components/AvailableModulesMenu.jsx @@ -39,13 +39,18 @@ const GRPCAT = { 'ml': 'lasers', 'c': 'projectiles', 'mc': 'projectiles', + 'advmc': 'projectiles', 'axmc': 'experimental', + 'axmce': 'experimental', + 'ntp': 'experimental', 'fc': 'projectiles', 'rfl': 'experimental', 'pa': 'projectiles', 'rg': 'projectiles', 'mr': 'ordnance', + 'amr': 'ordnance', 'axmr': 'experimental', + 'axmre': 'experimental', 'rcpl': 'experimental', 'dtl': 'experimental', 'tbsc': 'experimental', @@ -104,8 +109,8 @@ const CATEGORIES = { // Hardpoints 'lasers': ['pl', 'ul', 'bl'], - 'projectiles': ['mc', 'c', 'fc', 'pa', 'rg'], - 'ordnance': ['mr', 'tp', 'nl'], + 'projectiles': ['mc', 'advmc', 'c', 'fc', 'pa', 'rg'], + 'ordnance': ['mr', 'amr', 'tp', 'nl'], // Utilities 'sb': ['sb'], 'hs': ['hs'], @@ -113,7 +118,7 @@ const CATEGORIES = { 'defence': ['ch', 'po', 'ec'], 'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners // Experimental - 'experimental': ['axmc', 'axmr', 'rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr',], + 'experimental': ['axmc', 'axmce', 'axmr', 'axmre', 'ntp','rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr',], 'weapon stabilizers': ['ews'], // Guardian 'guardian': ['gpp', 'gpd', 'gpc', 'ggc', 'gsrp', 'gfsb', 'ghrp', 'gmrp', 'gsc'], @@ -213,16 +218,30 @@ export default class AvailableModulesMenu extends TranslatedComponent { if (categories.length === 1) { // Show category header instead of group header if (m && grp == m.grp) { - list.push(
this.groupElem = elem} key={category} + // If this is a missing module/weapon, skip it + if (m.grp == "mh" || m.grp == "mm"){ + continue; + } else { + list.push(
this.groupElem = elem} key={category} className={'select-category upp'}>{translate(category)}
); + } } else { - list.push(
{translate(category)}
); + if (category == "mh" || category == "mm"){ + continue; + } else { + list.push(
{translate(category)}
); + } } } else { // Show category header as well as group header if (!categoryHeader) { - list.push(
{translate(category)}
); - categoryHeader = true; + if (category == "mh" || category == "mm"){ + continue; + } + else { + list.push(
{translate(category)}
); + categoryHeader = true; + } } if (m && grp == m.grp) { list.push(
this.groupElem = elem} key={grp} @@ -241,7 +260,11 @@ export default class AvailableModulesMenu extends TranslatedComponent { } else if (i.mount === 'T') { mount = 'Turreted'; } - const fuzz = { grp, m: i, name: `${i.class}${i.rating}${mount ? ' ' + mount : ''} ${translate(grp)}` }; + let special = ''; + if (typeof(i.special) !== 'undefined') { + special = `(${translate(i.special)})`; + } + const fuzz = { grp, m: i, name: `${i.class}${i.rating}${mount ? ' ' + mount : ''} ${translate(grp)} ${translate(special)}` }; fuzzy.push(fuzz); } } @@ -298,6 +321,11 @@ export default class AvailableModulesMenu extends TranslatedComponent { let itemsOnThisRow = 0; for (let i = 0; i < sortedModules.length; i++) { let m = sortedModules[i]; + // If m.grp is mh or mm, or m.symbol contains 'Missing' skip it + if (m.grp == 'mh' || m.grp == 'mm' || (typeof(m.symbol) !== 'undefined' && m.symbol.includes("Missing"))) { + // If this is a missing module, skip it + continue; + } let mount = null; let disabled = false; prevName = m.name; diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx index 724d9fd5..e9edda43 100644 --- a/src/app/components/HardpointSlot.jsx +++ b/src/app/components/HardpointSlot.jsx @@ -136,6 +136,7 @@ export default class HardpointSlot extends Slot { {showModuleResistances && m.getThermalResistance() ?
{translate('thermres')}: {formats.pct(m.getThermalResistance())}
: null} {m.getIntegrity() ?
{translate('integrity')}: {formats.int(m.getIntegrity())}
: null} + {m.getInfo() ?
{translate(m.getInfo())}
: null} {m && validMods.length > 0 ?
this.modButton = modButton}>
: null }
; diff --git a/src/app/components/LineChart.jsx b/src/app/components/LineChart.jsx index b4113ee6..76078aa6 100644 --- a/src/app/components/LineChart.jsx +++ b/src/app/components/LineChart.jsx @@ -1,281 +1,281 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ContainerDimensions from 'react-container-dimensions'; -import * as d3 from 'd3'; -import TranslatedComponent from './TranslatedComponent'; - -const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 }; - -/** - * Line Chart - */ -export default class LineChart extends TranslatedComponent { - - static defaultProps = { - code: '', - xMin: 0, - yMin: 0, - points: 20, - colors: ['#ff8c0d'], - aspect: 0.5 - }; - - static propTypes = { - func: PropTypes.func.isRequired, - xLabel: PropTypes.string.isRequired, - xMin: PropTypes.number, - xMax: PropTypes.number.isRequired, - xUnit: PropTypes.string.isRequired, - xMark: PropTypes.number, - yLabel: PropTypes.string.isRequired, - yMin: PropTypes.number, - yMax: PropTypes.number.isRequired, - yUnit: PropTypes.string, - series: PropTypes.array, - colors: PropTypes.array, - points: PropTypes.number, - aspect: PropTypes.number, - code: PropTypes.string, - }; - - /** - * Constructor - * @param {Object} props React Component properties - * @param {Object} context React Component context - */ - constructor(props, context) { - super(props); - - this._updateDimensions = this._updateDimensions.bind(this); - this._updateSeries = this._updateSeries.bind(this); - this._tooltip = this._tooltip.bind(this); - this._showTip = this._showTip.bind(this); - this._hideTip = this._hideTip.bind(this); - this._moveTip = this._moveTip.bind(this); - - const series = props.series; - - let xScale = d3.scaleLinear(); - let yScale = d3.scaleLinear(); - let xAxisScale = d3.scaleLinear(); - - this.xAxis = d3.axisBottom(xAxisScale).tickSizeOuter(0); - this.yAxis = d3.axisLeft(yScale).ticks(6).tickSizeOuter(0); - - this.state = { - xScale, - xAxisScale, - yScale, - tipHeight: 2 + (1.2 * (series ? series.length : 0.8)), - }; - } - - /** - * Update tooltip content - * @param {number} xPos x coordinate - * @param {number} width current container width - */ - _tooltip(xPos, width) { - let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props; - let { xScale, yScale } = this.state; - let { formats, translate } = this.context.language; - let x0 = xScale.invert(xPos), - y0 = func(x0), - tips = this.tipContainer, - yTotal = 0, - flip = (xPos / width > 0.50), - tipWidth = 0, - tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height; - - - xPos = xScale(x0); // Clamp xPos - - tips.selectAll('text.text-tip.y').text(function(d, i) { - let yVal = series ? y0[series[i]] : y0; - yTotal += yVal; - return (series ? translate(series[i]) : '') + ' ' + formats.f2(yVal); - }).append('tspan').attr('class', 'metric').text(yUnit ? ' ' + yUnit : ''); - - tips.selectAll('text').each(function() { - if (this.getBBox().width > tipWidth) { - tipWidth = Math.ceil(this.getBBox().width); - } - }); - - let tipY = Math.floor(yScale(yTotal / (series ? series.length : 1)) - (tipHeightPx / 2)); - - tipWidth += 8; - tips.attr('transform', 'translate(' + xPos + ',' + tipY + ')'); - tips.selectAll('text.text-tip').attr('x', flip ? -12 : 12).style('text-anchor', flip ? 'end' : 'start'); - tips.selectAll('text.text-tip.x').text(formats.f2(x0)).append('tspan').attr('class', 'metric').text(' ' + xUnit); - tips.selectAll('rect').attr('width', tipWidth + 4).attr('x', flip ? -tipWidth - 12 : 8).attr('y', 0).style('text-anchor', flip ? 'end' : 'start'); - this.markersContainer.selectAll('circle').attr('cx', xPos).attr('cy', (d, i) => yScale(series ? y0[series[i]] : y0)); - } - - /** - * Update dimensions based on properties and scale - * @param {Object} props React Component properties - * @param {number} scale size ratio / scale - * @param {number} width current width of the container - * @returns {Object} calculated dimensions - */ - _updateDimensions(props, scale, width) { - const { xMax, xMin, yMin, yMax } = props; - const innerWidth = width - MARGIN.left - MARGIN.right; - const outerHeight = Math.round(width * props.aspect); - const innerHeight = outerHeight - MARGIN.top - MARGIN.bottom; - - this.state.xScale.range([0, innerWidth]).domain([xMin, xMax || 1]).clamp(true); - this.state.xAxisScale.range([0, innerWidth]).domain([xMin, xMax]).clamp(true); - this.state.yScale.range([innerHeight, 0]).domain([yMin, yMax + (yMax - yMin) * 0.1]); // 10% higher than maximum value for tooltip visibility - return { innerWidth, outerHeight, innerHeight }; - } - - /** - * Show tooltip - * @param {SyntheticEvent} e Event - */ - _showTip(e) { - e.preventDefault(); - this.tipContainer.style('display', null); - this.markersContainer.style('display', null); - this._moveTip(e); - } - - /** - * Move and update tooltip - * @param {SyntheticEvent} e Event - * @param {number} width current container width - */ - _moveTip(e, width) { - let clientX = e.touches ? e.touches[0].clientX : e.clientX; - this._tooltip(Math.round(clientX - e.currentTarget.getBoundingClientRect().left), width); - } - - /** - * Hide tooltip - * @param {SyntheticEvent} e Event - */ - _hideTip(e) { - e.preventDefault(); - this.tipContainer.style('display', 'none'); - this.markersContainer.style('display', 'none'); - } - - /** - * Update series generated from props - * @param {Object} props React Component properties - * @param {Object} state React Component state - */ - _updateSeries(props, state) { - let { func, xMin, xMax, series, points } = props; - let delta = (xMax - xMin) / points; - let seriesData = new Array(points); - - if (delta) { - seriesData = new Array(points); - for (let i = 0, x = xMin; i < points; i++) { - seriesData[i] = [x, func(x)]; - x += delta; - } - seriesData[points - 1] = [xMax, func(xMax)]; - } else { - let yVal = func(xMin); - seriesData = [[0, yVal], [1, yVal]]; - } - - const markerElems = []; - const detailElems = []; - const seriesLines = []; - for (let i = 0, l = series ? series.length : 1; i < l; i++) { - const yAccessor = series ? function(d) { return state.yScale(d[1][this]); }.bind(series[i]) : (d) => state.yScale(d[1]); - seriesLines.push(d3.line().x((d, i) => this.state.xScale(d[0])).y(yAccessor)); - detailElems.push(); - markerElems.push(); - } - - const tipHeight = 2 + (1.2 * (seriesLines ? seriesLines.length : 0.8)); - - this.setState({ markerElems, detailElems, seriesLines, seriesData, tipHeight }); - } - - /** - * Update dimensions and series data based on props and context. - */ - componentWillMount() { - this._updateSeries(this.props, this.state); - } - - /** - * Update state based on property and context changes - * @param {Object} nextProps Incoming/Next properties - * @param {Object} nextContext Incoming/Next conext - */ - componentWillReceiveProps(nextProps, nextContext) { - const props = this.props; - - if (props.code != nextProps.code) { - this._updateSeries(nextProps, this.state); - } - } - - /** - * Render the chart - * @return {React.Component} Chart SVG - */ - render() { - return ( - - { ({ width, height }) => { - const { innerWidth, outerHeight, innerHeight } = this._updateDimensions(this.props, this.context.sizeRatio, width, height); - const { xMin, xMax, xLabel, yLabel, xUnit, yUnit, xMark, colors } = this.props; - const { tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state; - const lines = seriesLines.map((line, i) => ).reverse(); - - const markX = xMark ? innerWidth * (xMark - xMin) / (xMax - xMin) : 0; - const xmark = xMark ? : ''; - return ( -
- - - {xmark} - {lines} - d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}> - - {xLabel} - ({xUnit}) - - - d3.select(elem).call(this.yAxis)}> - - {yLabel} - { yUnit && ({yUnit}) } - - - this.tipContainer = d3.select(g)} style={{ display: 'none' }}> - - {detailElems} - - this.markersContainer = d3.select(g)} style={{ display: 'none' }}> - {markerElems} - - this._moveTip(e, width)} - onTouchMove={e => this._moveTip(e, width)} - /> - - -
- ); - }} -
- ); - } -} +import React from 'react'; +import PropTypes from 'prop-types'; +import ContainerDimensions from 'react-container-dimensions'; +import * as d3 from 'd3'; +import TranslatedComponent from './TranslatedComponent'; + +const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 }; + +/** + * Line Chart + */ +export default class LineChart extends TranslatedComponent { + + static defaultProps = { + code: '', + xMin: 0, + yMin: 0, + points: 20, + colors: ['#ff8c0d'], + aspect: 0.5 + }; + + static propTypes = { + func: PropTypes.func.isRequired, + xLabel: PropTypes.string.isRequired, + xMin: PropTypes.number, + xMax: PropTypes.number.isRequired, + xUnit: PropTypes.string.isRequired, + xMark: PropTypes.number, + yLabel: PropTypes.string.isRequired, + yMin: PropTypes.number, + yMax: PropTypes.number.isRequired, + yUnit: PropTypes.string, + series: PropTypes.array, + colors: PropTypes.array, + points: PropTypes.number, + aspect: PropTypes.number, + code: PropTypes.string, + }; + + /** + * Constructor + * @param {Object} props React Component properties + * @param {Object} context React Component context + */ + constructor(props, context) { + super(props); + + this._updateDimensions = this._updateDimensions.bind(this); + this._updateSeries = this._updateSeries.bind(this); + this._tooltip = this._tooltip.bind(this); + this._showTip = this._showTip.bind(this); + this._hideTip = this._hideTip.bind(this); + this._moveTip = this._moveTip.bind(this); + + const series = props.series; + + let xScale = d3.scaleLinear(); + let yScale = d3.scaleLinear(); + let xAxisScale = d3.scaleLinear(); + + this.xAxis = d3.axisBottom(xAxisScale).tickSizeOuter(0); + this.yAxis = d3.axisLeft(yScale).ticks(6).tickSizeOuter(0); + + this.state = { + xScale, + xAxisScale, + yScale, + tipHeight: 2 + (1.2 * (series ? series.length : 0.8)), + }; + } + + /** + * Update tooltip content + * @param {number} xPos x coordinate + * @param {number} width current container width + */ + _tooltip(xPos, width) { + let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props; + let { xScale, yScale } = this.state; + let { formats, translate } = this.context.language; + let x0 = xScale.invert(xPos), + y0 = func(x0), + tips = this.tipContainer, + yTotal = 0, + flip = (xPos / width > 0.50), + tipWidth = 0, + tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height; + + + xPos = xScale(x0); // Clamp xPos + + tips.selectAll('text.text-tip.y').text(function(d, i) { + let yVal = series ? y0[series[i]] : y0; + yTotal += yVal; + return (series ? translate(series[i]) : '') + ' ' + formats.f2(yVal); + }).append('tspan').attr('class', 'metric').text(yUnit ? ' ' + yUnit : ''); + + tips.selectAll('text').each(function() { + if (this.getBBox().width > tipWidth) { + tipWidth = Math.ceil(this.getBBox().width); + } + }); + + let tipY = Math.floor(yScale(yTotal / (series ? series.length : 1)) - (tipHeightPx / 2)); + + tipWidth += 8; + tips.attr('transform', 'translate(' + xPos + ',' + tipY + ')'); + tips.selectAll('text.text-tip').attr('x', flip ? -12 : 12).style('text-anchor', flip ? 'end' : 'start'); + tips.selectAll('text.text-tip.x').text(formats.f2(x0)).append('tspan').attr('class', 'metric').text(' ' + xUnit); + tips.selectAll('rect').attr('width', tipWidth + 4).attr('x', flip ? -tipWidth - 12 : 8).attr('y', 0).style('text-anchor', flip ? 'end' : 'start'); + this.markersContainer.selectAll('circle').attr('cx', xPos).attr('cy', (d, i) => yScale(series ? y0[series[i]] : y0)); + } + + /** + * Update dimensions based on properties and scale + * @param {Object} props React Component properties + * @param {number} scale size ratio / scale + * @param {number} width current width of the container + * @returns {Object} calculated dimensions + */ + _updateDimensions(props, scale, width) { + const { xMax, xMin, yMin, yMax } = props; + const innerWidth = width - MARGIN.left - MARGIN.right; + const outerHeight = Math.round(width * props.aspect); + const innerHeight = outerHeight - MARGIN.top - MARGIN.bottom; + + this.state.xScale.range([0, innerWidth]).domain([xMin, xMax || 1]).clamp(true); + this.state.xAxisScale.range([0, innerWidth]).domain([xMin, xMax]).clamp(true); + this.state.yScale.range([innerHeight, 0]).domain([yMin, yMax + (yMax - yMin) * 0.1]); // 10% higher than maximum value for tooltip visibility + return { innerWidth, outerHeight, innerHeight }; + } + + /** + * Show tooltip + * @param {SyntheticEvent} e Event + */ + _showTip(e) { + e.preventDefault(); + this.tipContainer.style('display', null); + this.markersContainer.style('display', null); + this._moveTip(e); + } + + /** + * Move and update tooltip + * @param {SyntheticEvent} e Event + * @param {number} width current container width + */ + _moveTip(e, width) { + let clientX = e.touches ? e.touches[0].clientX : e.clientX; + this._tooltip(Math.round(clientX - e.currentTarget.getBoundingClientRect().left), width); + } + + /** + * Hide tooltip + * @param {SyntheticEvent} e Event + */ + _hideTip(e) { + e.preventDefault(); + this.tipContainer.style('display', 'none'); + this.markersContainer.style('display', 'none'); + } + + /** + * Update series generated from props + * @param {Object} props React Component properties + * @param {Object} state React Component state + */ + _updateSeries(props, state) { + let { func, xMin, xMax, series, points } = props; + let delta = (xMax - xMin) / points; + let seriesData = new Array(points); + + if (delta) { + seriesData = new Array(points); + for (let i = 0, x = xMin; i < points; i++) { + seriesData[i] = [x, func(x)]; + x += delta; + } + seriesData[points - 1] = [xMax, func(xMax)]; + } else { + let yVal = func(xMin); + seriesData = [[0, yVal], [1, yVal]]; + } + + const markerElems = []; + const detailElems = []; + const seriesLines = []; + for (let i = 0, l = series ? series.length : 1; i < l; i++) { + const yAccessor = series ? function(d) { return state.yScale(d[1][this]); }.bind(series[i]) : (d) => state.yScale(d[1]); + seriesLines.push(d3.line().x((d, i) => this.state.xScale(d[0])).y(yAccessor)); + detailElems.push(); + markerElems.push(); + } + + const tipHeight = 2 + (1.2 * (seriesLines ? seriesLines.length : 0.8)); + + this.setState({ markerElems, detailElems, seriesLines, seriesData, tipHeight }); + } + + /** + * Update dimensions and series data based on props and context. + */ + componentWillMount() { + this._updateSeries(this.props, this.state); + } + + /** + * Update state based on property and context changes + * @param {Object} nextProps Incoming/Next properties + * @param {Object} nextContext Incoming/Next conext + */ + componentWillReceiveProps(nextProps, nextContext) { + const props = this.props; + + if (props.code != nextProps.code) { + this._updateSeries(nextProps, this.state); + } + } + + /** + * Render the chart + * @return {React.Component} Chart SVG + */ + render() { + return ( + + { ({ width, height }) => { + const { innerWidth, outerHeight, innerHeight } = this._updateDimensions(this.props, this.context.sizeRatio, width, height); + const { xMin, xMax, xLabel, yLabel, xUnit, yUnit, xMark, colors } = this.props; + const { tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state; + const lines = seriesLines.map((line, i) => ).reverse(); + + const markX = xMark ? innerWidth * (xMark - xMin) / (xMax - xMin) : 0; + const xmark = xMark ? : ''; + return ( +
+ + + {xmark} + {lines} + d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}> + + {xLabel} + ({xUnit}) + + + d3.select(elem).call(this.yAxis)}> + + {yLabel} + { yUnit && ({yUnit}) } + + + this.tipContainer = d3.select(g)} style={{ display: 'none' }}> + + {detailElems} + + this.markersContainer = d3.select(g)} style={{ display: 'none' }}> + {markerElems} + + this._moveTip(e, width)} + onTouchMove={e => this._moveTip(e, width)} + /> + + +
+ ); + }} +
+ ); + } +} diff --git a/src/app/components/ModalShoppingList.jsx b/src/app/components/ModalShoppingList.jsx index cd2e2ec3..b1f21e15 100644 --- a/src/app/components/ModalShoppingList.jsx +++ b/src/app/components/ModalShoppingList.jsx @@ -12,7 +12,8 @@ const base64url = require('base64url'); export default class ModalShoppingList extends TranslatedComponent { static propTypes = { - ship: PropTypes.object.isRequired + ship: PropTypes.object.isRequired, + buildName: PropTypes.string }; /** @@ -90,8 +91,10 @@ export default class ModalShoppingList extends TranslatedComponent { request .get('http://localhost:44405/commanders') .end((err, res) => { + this.display = 'block'; if (err) { console.log(err); + this.display = 'none'; return this.setState({ failed: true }); } const cmdrs = JSON.parse(res.text); @@ -147,6 +150,34 @@ export default class ModalShoppingList extends TranslatedComponent { } } + /** + * Fix issues with the item name for bulkheads when sending to EDOMH + * @param {*} ship Ship object + * @param {*} item Item name + * @returns updated item name + */ + fixArmourItemNameForEDOMH(ship, item) { + // The module blueprint fdname contains "Armour_" it's a bulkhead and we need to pre-populate the item field with the correct name from the ship object + switch (ship.bulkheads.m.name){ + case "Lightweight Alloy": + item = ship.id + "_Armour_Grade1"; + break; + case "Reinforced Alloy": + item = ship.id + "_Armour_Grade2"; + break; + case "Military Grade Composite": + item = ship.id + "_Armour_Grade3"; + break; + case "Mirrored Surface Composite": + item = ship.id + "_Armour_Mirrored"; + break; + case "Reactive Surface Composite": + item = ship.id + "_Armour_Reactive"; + break; + } + return item; + } + /** * Send all blueprints to EDOMH. This is a modified copy of registerBPs because this.state.blueprints was empty when I tried to modify sendToEDEng and I couldn't figure out why * @param {Event} event React event @@ -154,6 +185,7 @@ export default class ModalShoppingList extends TranslatedComponent { sendToEDOMH(event) { event.preventDefault(); const ship = this.props.ship; + const buildName = this.props.buildName; let blueprints = []; //create the json @@ -166,20 +198,38 @@ export default class ModalShoppingList extends TranslatedComponent { continue; } if (module.m.blueprint.special) { + let item = ""; + // If the module blueprint fdname contains "Armour_" it's a bulkhead and we need to pre-populate the item field with the correct name from the ship object + if (module.m.blueprint.fdname.includes("Armour_")) { + item = this.fixArmourItemNameForEDOMH(ship, item) + } + else { + item = module.m.symbol; + } + blueprints.push({ - "item": module.m.symbol, + "item": item, "blueprint": module.m.blueprint.special.edname }); } - for (const g in module.m.blueprint.grades) { + for (let g in module.m.blueprint.grades) { if (!module.m.blueprint.grades.hasOwnProperty(g)) { continue; } - if (g < module.m.blueprint.grade) { + // We only want the grade that the module is currently at, not every grade up to that point + if (Number(g) !== module.m.blueprint.grade) { continue; } + let item = ""; + // If the module blueprint fdname contains "Armour_" it's a bulkhead and we need to pre-populate the item field with the correct name from the ship object + if (module.m.blueprint.fdname.includes("Armour_")) { + item = this.fixArmourItemNameForEDOMH(ship, item) + } + else { + item = module.m.symbol; + } blueprints.push({ - "item": module.m.symbol, + "item": item, "blueprint": module.m.blueprint.fdname, "grade": module.m.blueprint.grade, "highestGradePercentage":1.0 @@ -188,16 +238,18 @@ export default class ModalShoppingList extends TranslatedComponent { } } + let shipName = buildName + " - " + ship.name; + //create JSON to encode let baseJson = { "version":1, - "name":ship.name, // TO-DO: Import build name and put that here correctly + "name": shipName, // TO-DO: Import build name and put that here correctly "items": blueprints - } - + } + let JSONString = JSON.stringify(baseJson) let deflated = zlib.deflateSync(JSONString) - + //actually encode let link = base64url.encode(deflated) link = "edomh://coriolis/?" + link; @@ -219,14 +271,15 @@ export default class ModalShoppingList extends TranslatedComponent { if (!module.m.blueprint.grade || !module.m.blueprint.grades) { continue; } - for (const g in module.m.blueprint.grades) { + for (let g in module.m.blueprint.grades) { if (!module.m.blueprint.grades.hasOwnProperty(g)) { continue; } - if (g > module.m.blueprint.grade) { + // Ignore grades higher than the grade selected + if (Number(g) > module.m.blueprint.grade) { continue; } - for (const i in module.m.blueprint.grades[g].components) { + for (let i in module.m.blueprint.grades[g].components) { if (!module.m.blueprint.grades[g].components.hasOwnProperty(i)) { continue; } @@ -236,16 +289,16 @@ export default class ModalShoppingList extends TranslatedComponent { mats[i] = module.m.blueprint.grades[g].components[i] * this.state.matsPerGrade[g]; } } - if (module.m.blueprint.special) { - for (const j in module.m.blueprint.special.components) { - if (!module.m.blueprint.special.components.hasOwnProperty(j)) { - continue; - } - if (mats[j]) { - mats[j] += module.m.blueprint.special.components[j]; - } else { - mats[j] = module.m.blueprint.special.components[j]; - } + } + if (module.m.blueprint.special) { + for (const j in module.m.blueprint.special.components) { + if (!module.m.blueprint.special.components.hasOwnProperty(j)) { + continue; + } + if (mats[j]) { + mats[j] += module.m.blueprint.special.components[j]; + } else { + mats[j] = module.m.blueprint.special.components[j]; } } } @@ -303,7 +356,8 @@ export default class ModalShoppingList extends TranslatedComponent { this.sendToEDOMH = this.sendToEDOMH.bind(this); return
e.stopPropagation() }>

{translate('PHRASE_SHOPPING_MATS')}

- +

{translate('PHRASE_DIFFERENT_ROLLS')}

+ {/*
@@ -316,20 +370,26 @@ export default class ModalShoppingList extends TranslatedComponent {
- + */}