diff --git a/src/app/components/ModalShoppingList.jsx b/src/app/components/ModalShoppingList.jsx
new file mode 100644
index 00000000..7c53732a
--- /dev/null
+++ b/src/app/components/ModalShoppingList.jsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import TranslatedComponent from './TranslatedComponent';
+import ShortenUrl from '../utils/ShortenUrl';
+
+/**
+ * Permalink modal
+ */
+export default class ModalShoppingList extends TranslatedComponent {
+
+ static propTypes = {
+ mats: PropTypes.object.isRequired
+ };
+
+ /**
+ * Constructor
+ * @param {Object} props React Component properties
+ */
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ mats: props.mats,
+ matsList: ''
+ };
+ }
+
+ /**
+ * Convert mats object to string
+ */
+ renderMats() {
+ let matsString = '';
+ for (const i in this.state.mats) {
+ if (!this.state.mats.hasOwnProperty(i)) {
+ continue;
+ }
+ matsString += `${i}: ${this.state.mats[i]}\n`;
+ }
+ this.setState({ matsList: matsString });
+ }
+
+ /**
+ * Render the modal
+ * @return {React.Component} Modal Content
+ */
+ render() {
+ let translate = this.context.language.translate;
+ this.renderMats();
+ return
e.stopPropagation() }>
+
{translate('PHRASE_SHOPPING_MATS')}
+
+
+
+
;
+ }
+}
diff --git a/src/app/i18n/en.json b/src/app/i18n/en.json
index 5fa7de6d..4f8f621e 100644
--- a/src/app/i18n/en.json
+++ b/src/app/i18n/en.json
@@ -25,6 +25,7 @@
"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",
diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx
index 36759969..cf533cb5 100644
--- a/src/app/pages/OutfittingPage.jsx
+++ b/src/app/pages/OutfittingPage.jsx
@@ -9,7 +9,7 @@ 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 } from '../components/SvgIcons';
+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';
@@ -25,6 +25,7 @@ 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
@@ -506,6 +507,37 @@ export default class OutfittingPage extends Page {
window.open('https://eddb.io/station?s=' + shipId + '&m=' + modIds.join(','));
}
+ /**
+ * Generates the shopping list
+ */
+ _genShoppingList() {
+ const ship = this.state.ship;
+ let mats = {};
+ for (const module of ship.costList) {
+ if (module.type === 'SHIP') {
+ continue;
+ }
+ if (module.m && module.m.blueprint) {
+ if (!module.m.blueprint.grade || !module.m.blueprint.grades) {
+ continue;
+ }
+ for (const g in module.m.blueprint.grades) {
+ if (g > module.m.blueprint.grade) {
+ continue;
+ }
+ for (const i in module.m.blueprint.grades[g].components) {
+ if (mats[i]) {
+ mats[i] += module.m.blueprint.grades[g].components[i];
+ } else {
+ mats[i] = module.m.blueprint.grades[g].components[i];
+ }
+ }
+ }
+ }
+ }
+ this.context.showModal();
+ }
+
/**
* Handle Key Down
* @param {Event} e Keyboard Event
@@ -604,6 +636,9 @@ export default class OutfittingPage extends Page {
+