Remove comparison page

This commit is contained in:
Felix Linker
2021-12-26 23:01:17 +01:00
parent 23c264e321
commit 3db3b09c22
4 changed files with 1 additions and 729 deletions

View File

@@ -16,12 +16,9 @@ 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 ShipyardPage from './pages/ShipyardPage';
import ErrorDetails from './pages/ErrorDetails';
const request = require('superagent');
/**
* Coriolis App
*/
@@ -76,9 +73,6 @@ 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('/about', (r) => this._setPage(AboutPage, r));
Router('*', (r) => this._setPage(null, r));
}

View File

@@ -548,13 +548,6 @@ export default class Header extends TranslatedComponent {
{openedMenu == 'b' ? this._getBuildsMenu() : null}
</div>
<div className='l menu'>
<div className={cn('menu-header', { selected: openedMenu == 'comp', disabled: !hasBuilds })} onClick={hasBuilds && this._openComp}>
<StatsBars className={cn('warning', { 'warning-disabled': !hasBuilds })} /><span className='menu-item-label'>{translate('compare')}</span>
</div>
{openedMenu == 'comp' ? this._getComparisonsMenu() : null}
</div>
{/* TODO: Enable */}
{/* <div className='l menu'>
<div className={cn('menu-header', { selected: openedMenu == 'announce', disabled: this.props.announcements.length === 0 })} onClick={this.props.announcements.length !== 0 && this._openAnnounce}>

View File

@@ -1,626 +0,0 @@
import React from 'react';
import Page from './Page';
import Router from '../Router';
import cn from 'classnames';
import { Ships } from 'coriolis-data/dist';
import { fromComparison, toComparison } from '../shipyard/Serializer';
import Persist from '../stores/Persist';
import { SizeMap, ShipFacets } from '../shipyard/Constants';
import ComparisonTable from '../components/ComparisonTable';
import BarChart from '../components/BarChart';
import ModalCompare from '../components/ModalCompare';
import ModalExport from '../components/ModalExport';
import ModalPermalink from '../components/ModalPermalink';
import ModalImport from '../components/ModalImport';
import {
FloppyDisk,
Bin,
Download,
Embed,
Rocket,
LinkIcon
} from '../components/SvgIcons';
import ShortenUrl from '../utils/ShortenUrl';
import { comparisonBBCode } from '../utils/BBCode';
const browser = require('detect-browser');
/**
* Creates a comparator based on the specified predicate
* @param {string} predicate Predicate / propterty name
* @return {Function} Comparator
*/
function sortBy(predicate) {
return (a, b) => {
if (a[predicate] === b[predicate]) {
if (a.name == b.name) {
a.buildName.toLowerCase() > b.buildName.toLowerCase() ? 1 : -1;
}
return a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1;
}
if (typeof a[predicate] == 'string') {
return a[predicate].toLowerCase() > b[predicate].toLowerCase() ? 1 : -1;
}
return a[predicate] > b[predicate] ? 1 : -1;
};
}
/**
* Comparison Page
*/
export default class ComparisonPage extends Page {
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props, context);
this._sortShips = this._sortShips.bind(this);
this._buildsSelected = this._buildsSelected.bind(this);
this._updateDiscounts = this._updateDiscounts.bind(this);
this.state = this._initState(context);
}
/**
* [Re]Create initial state from context
* @param {context} context React component context
* @return {Object} New state object
*/
_initState(context) {
let defaultFacets = [13, 12, 11, 9, 6, 4, 1, 3, 2]; // Reverse order of Armour, Shields, Speed, Jump Range, Cargo Capacity, Cost, DPS, EPS, HPS
let params = context.route.params;
let code = params.code;
let name = params.name ? decodeURIComponent(params.name) : null;
let newName = '';
let compareMode = !code;
let builds = [];
let saved = false;
let predicate = 'name';
let desc = false;
let importObj = {};
if (compareMode) {
if (name == 'all') {
let allBuilds = Persist.getBuilds();
newName = name;
for (let shipId in allBuilds) {
for (let buildName in allBuilds[shipId]) {
if (buildName && allBuilds[shipId][buildName]) {
builds.push(
this._createBuild(
shipId,
buildName,
allBuilds[shipId][buildName]
)
);
}
}
}
} else {
let comparisonData = Persist.getComparison(name);
if (comparisonData) {
defaultFacets = comparisonData.facets;
comparisonData.builds.forEach(b =>
builds.push(this._createBuild(b.shipId, b.buildName))
);
saved = true;
newName = name;
}
}
} else {
try {
let comparisonData = toComparison(code);
defaultFacets = comparisonData.f;
newName = name = comparisonData.n;
predicate = comparisonData.p;
desc = comparisonData.d;
comparisonData.b.forEach(build => {
builds.push(this._createBuild(build.s, build.n, build.c));
if (!importObj[build.s]) {
importObj[build.s] = {};
}
importObj[build.s][build.n] = build.c;
});
} catch (e) {
throw { type: 'bad-comparison', message: e.message, details: e };
}
}
let facets = [];
let selectedLength = defaultFacets.length;
let selectedFacets = new Array(selectedLength);
for (let i = 0; i < ShipFacets.length; i++) {
let facet = Object.assign({}, ShipFacets[i]);
let defaultIndex = defaultFacets.indexOf(facet.i);
if (defaultIndex == -1) {
facets.push(facet);
} else {
facet.active = true;
selectedFacets[selectedLength - defaultIndex - 1] = facet;
}
}
facets = selectedFacets.concat(facets);
builds.sort(sortBy(predicate));
return {
title: 'Coriolis EDCD Edition - Compare',
predicate,
desc,
facets,
builds,
compareMode,
code,
name,
newName,
saved,
importObj
};
}
/**
* Create a Ship instance / build
* @param {string} id Ship Id
* @param {name} name Build name
* @param {string} code Optional - Serialized ship code
* @return {Object} Ship instance with build name
*/
_createBuild(id, name, code) {
code = code ? code : Persist.getBuild(id, name); // Retrieve build code if not passed
if (!code) {
// No build found
return;
}
let data = Ships[id]; // Get ship properties
let b = new Ship(id, data.properties, data.slots); // Create a new Ship instance
b.buildFrom(code); // Populate components from code
b.buildName = name;
b.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
return b;
}
/**
* Update state with the specified sort predicates
* @param {String} predicate Sort predicate - property name
*/
_sortShips(predicate) {
let { builds, desc } = this.state;
if (this.state.predicate == predicate) {
desc = !desc;
}
builds.sort(sortBy(predicate));
if (desc) {
builds.reverse();
}
this.setState({ predicate, desc });
}
/**
* Show selected builds modal
*/
_selectBuilds() {
this.context.showModal(
<ModalCompare
onSelect={this._buildsSelected}
builds={this.state.builds}
/>
);
}
/**
* Update selected builds with new list
* @param {Array} newBuilds List of new builds
*/
_buildsSelected(newBuilds) {
this.context.hideModal();
let builds = [];
for (let b of newBuilds) {
builds.push(this._createBuild(b.id, b.buildName));
}
this.setState({ builds, saved: false });
}
/**
* Toggle facet display
* @param {string} facet Facet / Ship Property
*/
_toggleFacet(facet) {
facet.active = !facet.active;
this.setState({ facets: [].concat(this.state.facets), saved: false });
}
/**
* Handle facet drag
* @param {Event} e Drag Event
*/
_facetDrag(e) {
this.nodeAfter = false;
this.dragged = e.currentTarget;
let placeholder = (this.placeholder = document.createElement('li'));
placeholder.style.width = Math.round(this.dragged.offsetWidth) + 'px';
placeholder.className = 'facet-placeholder';
if (!browser || (browser.name !== 'edge' && browser.name !== 'ie')) {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', e.currentTarget);
}
}
/**
* Handle facet drop
* @param {Event} e Drop Event
*/
_facetDrop(e) {
this.dragged.parentNode.removeChild(this.placeholder);
let facets = this.state.facets;
let frm = Number(this.dragged.dataset.i);
let to = Number(this.over.dataset.i);
if (frm < to) {
to--;
}
if (this.nodeAfter) {
to++;
}
facets.splice(to, 0, facets.splice(frm, 1)[0]);
this.dragged.style.display = null;
this.setState({ facets: [].concat(facets), saved: false });
}
/**
* Handle facet drag over
* @param {Event} e Drag over Event
*/
_facetDragOver(e) {
e.preventDefault();
if (e.target.className == 'facet-placeholder') {
return;
} else if (e.target != e.currentTarget) {
this.over = e.target;
this.dragged.style.display = 'none';
let relX = e.clientX - this.over.getBoundingClientRect().left;
let width = this.over.offsetWidth / 2;
let parent = e.target.parentNode;
if (parent == e.currentTarget) {
if (relX > width && this.dragged != e.target) {
this.nodeAfter = true;
parent.insertBefore(this.placeholder, e.target.nextElementSibling);
} else {
this.nodeAfter = false;
parent.insertBefore(this.placeholder, e.target);
}
}
}
}
/**
* Handle name change and update state
* @param {SyntheticEvent} e Event
*/
_onNameChange(e) {
this.setState({ newName: e.target.value, saved: false });
}
/**
* Delete the current comparison
*/
_delete() {
Persist.deleteComparison(this.state.name);
Router.go('/compare');
}
/**
* Import the comparison builds
*/
_import() {
let builds = {};
for (let ship of this.state.builds) {
if (!builds[ship.id]) {
builds[ship.id] = {};
}
builds[ship.id][ship.buildName] = ship.toString();
}
this.context.showModal(<ModalImport builds={builds} />);
}
/**
* Save the current comparison
*/
_save() {
let { newName, builds, facets } = this.state;
let selectedFacets = [];
facets.forEach(f => {
if (f.active) {
selectedFacets.unshift(f.i);
}
});
Persist.saveComparison(newName, builds, selectedFacets);
Router.replace(`/compare/${encodeURIComponent(this.state.newName)}`);
this.setState({ name: newName, saved: true });
}
/**
* Serialize and generate a long URL for the current comparison
* @return {string} URL for serialized comparison
*/
_buildUrl() {
let { facets, builds, name, predicate, desc } = this.state;
let selectedFacets = [];
for (let f of facets) {
if (f.active) {
selectedFacets.unshift(f.i);
}
}
let code = fromComparison(name, builds, selectedFacets, predicate, desc);
let loc = window.location;
return (
loc.protocol +
'//' +
loc.host +
'/comparison?code=' +
encodeURIComponent(code)
);
}
/**
* Generates the long permalink URL
*/
_genPermalink() {
this.context.showModal(<ModalPermalink url={this._buildUrl()} />);
}
/**
* Generate E:D Forum BBCode and show in the export modal
*/
_genBBcode() {
let { translate, formats } = this.context.language;
let { facets, builds } = this.state;
let generator = callback => {
let url = this._buildUrl();
ShortenUrl(
url,
shortenedUrl =>
callback(
comparisonBBCode(translate, formats, facets, builds, shortenedUrl)
),
error =>
callback(comparisonBBCode(translate, formats, facets, builds, url))
);
};
this.context.showModal(
<ModalExport
title={translate('forum') + ' BBCode'}
generator={generator}
/>
);
}
/**
* Update dimenions from rendered DOM
*/
_updateDimensions() {
this.setState({
chartWidth: this.chartRef.offsetWidth
});
}
/**
* Update all ship costs on disount change
*/
_updateDiscounts() {
let shipDiscount = Persist.getShipDiscount();
let moduleDiscount = Persist.getModuleDiscount();
let builds = [];
for (let b of this.state.builds) {
builds.push(b.applyDiscounts(shipDiscount, moduleDiscount));
}
this.setState({ builds });
}
/**
* 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(nextContext));
}
}
/**
* Add listeners when about to mount
*/
componentWillMount() {
this.resizeListener = this.context.onWindowResize(this._updateDimensions);
this.persistListener = Persist.addListener(
'discounts',
this._updateDiscounts
);
}
/**
* Trigger DOM updates on mount
*/
componentDidMount() {
this._updateDimensions();
}
/**
* Remove listeners on unmount
*/
componentWillUnmount() {
this.resizeListener.remove();
this.persistListener.remove();
}
/**
* Render the Page
* @return {React.Component} The page contents
*/
renderPage() {
let translate = this.context.language.translate;
let compareHeader;
let {
newName,
name,
saved,
builds,
facets,
predicate,
desc,
chartWidth
} = this.state;
if (this.state.compareMode) {
compareHeader = (
<tr>
<td className="head">{translate('comparison')}</td>
<td>
<input
value={newName}
onChange={this._onNameChange}
placeholder={translate('Enter Name')}
maxLength="50"
/>
<button
onClick={this._save}
disabled={!newName || newName == 'all' || saved}
>
<FloppyDisk className="lg" />
<span className="button-lbl">{translate('save')}</span>
</button>
<button onClick={this._delete} disabled={name == 'all' || !saved}>
<Bin className="lg warning" />
</button>
<button onClick={this._selectBuilds}>
<Rocket className="lg" />
<span className="button-lbl">{translate('builds')}</span>
</button>
<button
className="r"
onClick={this._genPermalink}
disabled={builds.length == 0}
>
<LinkIcon className="lg" />
<span className="button-lbl">{translate('permalink')}</span>
</button>
<button
className="r"
onClick={this._genBBcode}
disabled={builds.length == 0}
>
<Embed className="lg" />
<span className="button-lbl">{translate('forum')}</span>
</button>
</td>
</tr>
);
} else {
compareHeader = (
<tr>
<td className="head">{translate('comparison')}</td>
<td>
<h3>{name}</h3>
<button className="r" onClick={this._import}>
<Download className="lg" />
{translate('import')}
</button>
</td>
</tr>
);
}
return (
<div
className={'page'}
style={{ fontSize: this.context.sizeRatio + 'em' }}
>
<table id="comparison">
<tbody>
{compareHeader}
<tr key="facets">
<td className="head">{translate('compare')}</td>
<td>
<ul id="facet-container" onDragOver={this._facetDragOver}>
{facets.map((f, i) => (
<li
key={f.title}
data-i={i}
draggable="true"
onDragStart={this._facetDrag}
onDragEnd={this._facetDrop}
className={cn('facet', { active: f.active })}
onClick={this._toggleFacet.bind(this, f)}
>
{'↔ ' + translate(f.title)}
</li>
))}
</ul>
</td>
</tr>
</tbody>
</table>
<ComparisonTable
builds={builds}
facets={facets}
onSort={this._sortShips}
predicate={predicate}
desc={desc}
/>
{!builds.length ? (
<div className="chart" ref={node => (this.chartRef = node)}>
{translate('PHRASE_NO_BUILDS')}
</div>
) : (
facets.filter(f => f.active).map((f, i) => (
<div
key={f.title}
className="chart"
ref={i == 0 ? node => (this.chartRef = node) : null}
>
<h3
className="ptr"
onClick={this._sortShips.bind(this, f.props[0])}
>
{translate(f.title)}
</h3>
<BarChart
width={chartWidth}
data={builds}
properties={f.props}
labels={f.lbls}
unit={translate(f.unit)}
format={f.fmt}
title={translate(f.title)}
predicate={predicate}
desc={desc}
/>
</div>
))
)}
</div>
);
}
}

View File

@@ -2,7 +2,6 @@ import { EventEmitter } from 'fbemitter';
import { Insurance } from '../shipyard/Constants';
const LS_KEY_BUILDS = 'builds';
const LS_KEY_COMPARISONS = 'comparisons';
const LS_KEY_LANG = 'NG_TRANSLATE_LANG_KEY';
const LS_KEY_COST_TAB = 'costTab';
const LS_KEY_CMDR_NAME = 'cmdrName';
@@ -93,7 +92,6 @@ export class Persist extends EventEmitter {
let shipDiscount = _get(LS_KEY_SHIP_DISCOUNT);
let moduleDiscount = _get(LS_KEY_MOD_DISCOUNT);
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);
@@ -102,7 +100,6 @@ export class Persist extends EventEmitter {
this.shipDiscount = !isNaN(shipDiscount) && shipDiscount < 1 ? shipDiscount * 1 : 0;
this.moduleDiscount = !isNaN(moduleDiscount) && moduleDiscount < 1 ? moduleDiscount * 1 : 0;
this.builds = buildJson && typeof buildJson == 'object' ? buildJson : {};
this.comparisons = comparisonJson && typeof comparisonJson == 'object' ? comparisonJson : {};
this.costTab = _getString(LS_KEY_COST_TAB);
this.outfittingTab = _getString(LS_KEY_OUTFITTING_TAB);
this.state = _get(LS_KEY_STATE);
@@ -137,10 +134,6 @@ export class Persist extends EventEmitter {
this.builds = newValue ? JSON.parse(newValue) : {};
this.emit('builds');
break;
case LS_KEY_COMPARISONS:
this.comparisons = newValue ? JSON.parse(newValue) : {};
this.emit('comparisons');
break;
case LS_KEY_LANG:
this.langCode = newValue;
this.emit('language', newValue);
@@ -334,97 +327,16 @@ export class Persist extends EventEmitter {
delete this.builds[shipId];
}
_put(LS_KEY_BUILDS, this.builds);
// Check if the build was used in existing comparisons
let comps = this.comparisons;
for (let c in comps) {
for (let i = 0; i < comps[c].builds.length; i++) { // For all builds in the current comparison
if (comps[c].builds[i].shipId == shipId && comps[c].builds[i].buildName == name) {
comps[c].builds.splice(i, 1);
break; // A build is unique per comparison
}
}
}
_put(LS_KEY_COMPARISONS, this.comparisons);
this.emit('builds');
}
}
/**
* Persist a comparison in localstorage.
*
* @param {String} name The name of the comparison
* @param {array} builds Array of builds
* @param {array} facets Array of facet indices
*/
saveComparison(name, builds, facets) {
if (!this.comparisons[name]) {
this.comparisons[name] = {};
}
this.comparisons[name] = {
facets,
builds: builds.map(b => { return { shipId: b.id || b.shipId, buildName: b.buildName }; })
};
_put(LS_KEY_COMPARISONS, this.comparisons);
this.emit('comparisons');
}
/**
* Get a comparison
* @param {String} name Comparison name
* @return {Object} Object containing array of facets and ship id + build names
*/
getComparison(name) {
if (this.comparisons[name]) {
return this.comparisons[name];
}
return null;
}
/**
* Get all saved comparisons
* @return {Object} All comparisons
*/
getComparisons() {
return this.comparisons;
}
/**
* Check if a comparison has been saved
* @param {String} name Comparison name
* @return {Boolean} True if a comparison has been saved
*/
hasComparison(name) {
return !!this.comparisons[name];
}
/**
* Check if any comparisons have been saved
* @return {Boolean} True if any comparisons have been saved
*/
hasComparisons() {
return Object.keys(this.comparisons).length > 0;
}
/**
* Removes the comparison from localstorage.
* @param {String} name Comparison name
*/
deleteComparison(name) {
if (this.comparisons[name]) {
delete this.comparisons[name];
_put(LS_KEY_COMPARISONS, this.comparisons);
this.emit('comparisons');
}
}
/**
* Delete all builds and comparisons from localStorage
* Delete all builds from localStorage
*/
deleteAll() {
this.builds = {};
this.comparisons = {};
_put(LS_KEY_BUILDS, {});
_put(LS_KEY_COMPARISONS, {});
this.emit('deletedAll');
}
@@ -435,7 +347,6 @@ export class Persist extends EventEmitter {
getAll() {
let data = {};
data[LS_KEY_BUILDS] = this.getBuilds();
data[LS_KEY_COMPARISONS] = this.getComparisons();
data[LS_KEY_INSURANCE] = this.getInsurance();
data[LS_KEY_SHIP_DISCOUNT] = this.shipDiscount;
data[LS_KEY_MOD_DISCOUNT] = this.moduleDiscount;