Continued porting to react, approaching beta

This commit is contained in:
Colin McLeod
2016-01-21 22:06:05 -08:00
parent 653cb30dd9
commit 8227a4e361
86 changed files with 3810 additions and 2030 deletions

View File

@@ -1,22 +1,35 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import Page from './Page';
import Router from '../Router';
import cn from 'classnames';
import { Ships } from 'coriolis-data';
import Ship from '../shipyard/Ship';
import Serializer from '../shipyard/Serializer';
import InterfaceEvents from '../utils/InterfaceEvents';
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 { FloppyDisk, Bin, Download, Embed, Rocket, LinkIcon } from '../components/SvgIcons';
import ShortenUrl from '../utils/ShortenUrl';
import { comparisonBBCode } from '../utils/BBCode';
/**
* 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]) {
return 0;
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;
@@ -25,23 +38,35 @@ function sortBy(predicate) {
};
}
/**
* 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.state = this._initState(props, context);
this.state = this._initState(context);
}
_initState(props, context) {
/**
* [Re]Create initial state from context
* @param {context} context React component context
* @return {Object} New state object
*/
_initState(context) {
let defaultFacets = [9, 6, 4, 1, 3, 2]; // Reverse order of Armour, Shields, Speed, Jump Range, Cargo Capacity, Cost
let params = context.route.params;
let code = params.code;
let name = params.name ? decodeURIComponent(params.name) : null;
let newName = '';
let compareMode = !code;
let facets = [];
let builds = [];
let saved = false;
let predicate = 'name';
@@ -58,7 +83,6 @@ export default class ComparisonPage extends Page {
}
}
} else {
let comparisonData = Persist.getComparison(name);
if (comparisonData) {
defaultFacets = comparisonData.facets;
@@ -69,7 +93,7 @@ export default class ComparisonPage extends Page {
}
} else {
try {
let comparisonData = Serializer.toComparison(code);
let comparisonData = toComparison(code);
defaultFacets = comparisonData.f;
newName = name = comparisonData.n;
predicate = comparisonData.p;
@@ -86,20 +110,22 @@ export default class ComparisonPage extends Page {
}
}
let facets = [];
let selectedLength = defaultFacets.length;
let selectedFacets = new Array(selectedLength);
for (let i = 0; i < ShipFacets.length; i++) {
facets.push(Object.assign({ index: i }, ShipFacets[i]));
}
let selectedFacets = [];
for (let fi of defaultFacets) {
let facet = facets.splice(fi, 1)[0];
facet.active = true;
selectedFacets.unshift(facet);
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);
console.log(selectedFacets);
builds.sort(sortBy(predicate));
return {
@@ -116,7 +142,13 @@ console.log(selectedFacets);
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
@@ -132,8 +164,8 @@ console.log(selectedFacets);
};
/**
* Sort ships
* @param {object} key Sort predicate
* Update state with the specified sort predicates
* @param {String} predicate Sort predicate - property name
*/
_sortShips(predicate) {
let { builds, desc } = this.state;
@@ -150,38 +182,54 @@ console.log(selectedFacets);
this.setState({ predicate, desc });
};
/**
* Show selected builds modal
*/
_selectBuilds() {
InterfaceEvents.showModal(React.cloneElement(
<ModalCompare onSelect={this._buildsSelected}/>,
{ builds: this.state.builds }
));
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) {
InterfaceEvents.hideModal();
this.context.hideModal();
let builds = [];
for (let b of newBuilds) {
builds.push(this._createBuild(b.id, b.buildName));
}
this.setState({ builds });
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.dragged = e.currentTarget;
let placeholder = this.placeholder = document.createElement("li");
let placeholder = this.placeholder = document.createElement('li');
placeholder.style.width = this.dragged.offsetWidth + 'px';
placeholder.className = "facet-placeholder";
placeholder.className = 'facet-placeholder';
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData("text/html", e.currentTarget);
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;
@@ -197,18 +245,22 @@ console.log(selectedFacets);
facets.splice(to, 0, facets.splice(frm, 1)[0]);
this.dragged.style.display = null;
this.setState({ facets: [].concat(facets) });
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") {
if(e.target.className == 'facet-placeholder') {
return;
}
this.over = e.target;
this.dragged.style.display = "none";
this.dragged.style.display = 'none';
let relX = e.clientX - this.over.getBoundingClientRect().left;
let width = this.over.offsetWidth / 2;
let parent = e.target.parentNode;
@@ -217,71 +269,149 @@ console.log(selectedFacets);
if(relX > width) {
this.nodeAfter = true;
parent.insertBefore(this.placeholder, e.target.nextElementSibling);
}
else {
} 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() {
// TODO: Implement
}
/**
* Save the current comparison
*/
_save() {
let { newName, builds, facets } = this.state;
let selectedFacets = [];
facets.forEach((f) => {
if (f.active) {
selectedFacets.unshift(f.index);
selectedFacets.unshift(f.i);
}
});
console.log(selectedFacets);
//Persist.saveComparison(newName, builds, selectedFacets);
Router.replace(`/compare/${encodeURIComponent(this.state.newName)}`)
this.setState ({ name: newName, saved: true });
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}`;
}
/**
* Generates the long permalink URL
* @return {string} The long permalink URL
*/
_genPermalink() {
let { facets, builds, name, predicate, desc } = this.state;
let selectedFacets = [];
for (let f of facets){
if (f.active) {
selectedFacets.unshift(f.index);
}
}
let code = Serializer.fromComparison(name, builds, selectedFacets, predicate, desc);
// send code to permalink modal
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: findDOMNode(this.refs.chartRef).offsetWidth
});
}
/**
* 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));
this.setState(this._initState(nextContext));
}
}
/**
* Add listeners when about to mount
*/
componentWillMount() {
this.resizeListener = this.context.onWindowResize(this._updateDimensions);
}
/**
* Trigger DOM updates on mount
*/
componentDidMount() {
this._updateDimensions();
}
/**
* Remove listeners on unmount
*/
componentWillUnmount() {
this.resizeListener.remove();
}
/**
* Render the Page
* @return {React.Component} The page contents
*/
render() {
let translate = this.context.language.translate;
let compareHeader;
let {newName, name, saved, builds, facets, predicate, desc } = this.state;
let { newName, name, saved, builds, facets, predicate, desc, chartWidth } = this.state;
if (this.state.compareMode) {
compareHeader = <tr>
@@ -295,10 +425,10 @@ console.log(selectedFacets);
<button onClick={this._selectBuilds}>
<Rocket className='lg'/><span className='button-lbl'>{translate('builds')}</span>
</button>
<button className='r' ng-click='permalink($event)' ng-disabled='builds.length == 0'>
<button className='r' onClick={this._genPermalink} disabled={builds.length == 0}>
<LinkIcon className='lg'/><span className='button-lbl'>{translate('permalink')}</span>
</button>
<button className='r' ng-click='embed($event)' ng-disabled='builds.length == 0'>
<button className='r' onClick={this._genBBcode} disabled={builds.length == 0}>
<Embed className='lg'/><span className='button-lbl'>{translate('forum')}</span>
</button>
</td>
@@ -314,7 +444,7 @@ console.log(selectedFacets);
}
return (
<div className={'page'} style={{ fontSize: this.context.sizeRatio + 'em'}}>
<div className={'page'} style={{ fontSize: this.context.sizeRatio + 'em' }}>
<table id='comparison'>
<tbody>
{compareHeader}
@@ -323,7 +453,7 @@ console.log(selectedFacets);
<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)}>
<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>
)}
@@ -335,9 +465,23 @@ console.log(selectedFacets);
<ComparisonTable builds={builds} facets={facets} onSort={this._sortShips} predicate={predicate} desc={desc} />
{/*<div ng-repeat='f in facets | filter:{active:true}' ng-if='builds.length > 0' className='chart' bar-chart facet='f' data='builds'>
<h3 ng-click='sort(f.props[0])' >{{f.title | translate}}</h3>
</div>*/}
{!builds.length ?
<div className='chart' ref={'chartRef'}>{translate('PHRASE_NO_BUILDS')}</div> :
facets.filter((f) => f.active).map((f, i) =>
<div key={f.title} className='chart' ref={ i == 0 ? 'chartRef' : null}>
<h3 className='ptr' onClick={this._sortShips.bind(this, f.props[0])}>{translate(f.title)}</h3>
<BarChart
width={chartWidth}
data={builds}
properties={f.props}
unit={translate(f.unit)}
format={f.fmt}
label={translate(f.title)}
predicate={predicate}
desc={desc}
/>
</div>
)}
</div>
);