mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-08 22:33:24 +00:00
Add variable discount support
This commit is contained in:
@@ -303,9 +303,9 @@ export default class CostSection extends TranslatedComponent {
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th colSpan='2' className='sortable le' onClick={this._sortCostBy.bind(this,'m')}>
|
||||
{translate('component')}
|
||||
{shipDiscount < 1 && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct1(1 - shipDiscount)}]`}</u>}
|
||||
{moduleDiscount < 1 && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct1(1 - moduleDiscount)}]`}</u>}
|
||||
{translate('module')}
|
||||
{shipDiscount && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct(shipDiscount)}]`}</u>}
|
||||
{moduleDiscount && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u>}
|
||||
</th>
|
||||
<th className='sortable le' onClick={this._sortCostBy.bind(this, 'cr')} >{translate('credits')}</th>
|
||||
</tr>
|
||||
@@ -363,7 +363,7 @@ export default class CostSection extends TranslatedComponent {
|
||||
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'buyName')}>{translate('buy')}</th>
|
||||
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'cr')}>
|
||||
{translate('net cost')}
|
||||
{moduleDiscount < 1 && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct1(1 - moduleDiscount)}]`}</u>}
|
||||
{moduleDiscount < 1 && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u>}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -472,7 +472,7 @@ export default class CostSection extends TranslatedComponent {
|
||||
<th colSpan='2' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'm')} >{translate('module')}</th>
|
||||
<th colSpan='1' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'max')} >{translate('qty')}</th>
|
||||
<th colSpan='1' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'cost')} >{translate('unit cost')}</th>
|
||||
<th className='sortable le' onClick={this._sortAmmoBy.bind(this, 'total')}>{translate('total cost')}</th>
|
||||
<th className='sortable le' onClick={this._sortAmmoBy.bind(this, 'total')}>{translate('subtotal')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Languages } from '../i18n/Language';
|
||||
import { Insurance, Discounts } from '../shipyard/Constants';
|
||||
import { Insurance } from '../shipyard/Constants';
|
||||
import Link from './Link';
|
||||
import ActiveLink from './ActiveLink';
|
||||
import cn from 'classnames';
|
||||
@@ -18,6 +18,36 @@ import { outfitURL } from '../utils/UrlGenerators';
|
||||
const SIZE_MIN = 0.65;
|
||||
const SIZE_RANGE = 0.55;
|
||||
|
||||
/**
|
||||
* Normalize percentages to 'clean' values
|
||||
* @param {Number} val Percentage value
|
||||
* @return {Number} Normalized value
|
||||
*/
|
||||
function normalizePercent(val) {
|
||||
if (val === '' || isNaN(val)) {
|
||||
return 0;
|
||||
}
|
||||
val = Math.round(val * 100) / 100;
|
||||
return val >= 100 ? 100 : val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rounds the value to the nearest quarter (0, 0.25, 0.5, 0.75)
|
||||
* @param {Number} val Value
|
||||
* @return {Number} Rounded value
|
||||
*/
|
||||
function nearestQtrPct(val) {
|
||||
return Math.round(val * 4) / 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select all text in a field
|
||||
* @param {SyntheticEvent} e Event
|
||||
*/
|
||||
function selectAll(e) {
|
||||
e.target.select();
|
||||
}
|
||||
|
||||
/**
|
||||
* Coriolis App Header section / menus
|
||||
*/
|
||||
@@ -34,15 +64,22 @@ export default class Header extends TranslatedComponent {
|
||||
|
||||
this._setLanguage = this._setLanguage.bind(this);
|
||||
this._setInsurance = this._setInsurance.bind(this);
|
||||
|
||||
this._setShipDiscount = this._setShipDiscount.bind(this);
|
||||
this._changeShipDiscount = this._changeShipDiscount.bind(this);
|
||||
this._kpShipDiscount = this._kpShipDiscount.bind(this);
|
||||
this._setModuleDiscount = this._setModuleDiscount.bind(this);
|
||||
this._changeModuleDiscount = this._changeModuleDiscount.bind(this);
|
||||
this._kpModuleDiscount = this._kpModuleDiscount.bind(this);
|
||||
this._openShips = this._openMenu.bind(this, 's');
|
||||
this._openBuilds = this._openMenu.bind(this, 'b');
|
||||
this._openComp = this._openMenu.bind(this, 'comp');
|
||||
this._openSettings = this._openMenu.bind(this, 'settings');
|
||||
|
||||
this.languageOptions = [];
|
||||
this.insuranceOptions = [];
|
||||
this.discountOptions = [];
|
||||
this.state = {
|
||||
shipDiscount: normalizePercent(Persist.getShipDiscount() * 100),
|
||||
moduleDiscount: normalizePercent(Persist.getModuleDiscount() * 100),
|
||||
};
|
||||
|
||||
let translate = context.language.translate;
|
||||
|
||||
@@ -53,10 +90,6 @@ export default class Header extends TranslatedComponent {
|
||||
for (let name in Insurance) {
|
||||
this.insuranceOptions.push(<option key={name} value={name}>{translate(name)}</option>);
|
||||
}
|
||||
|
||||
for (let name in Discounts) {
|
||||
this.discountOptions.push(<option key={name} value={Discounts[name]}>{name}</option>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,18 +102,90 @@ export default class Header extends TranslatedComponent {
|
||||
|
||||
/**
|
||||
* Update the Module discount
|
||||
* @param {SyntheticEvent} e Event
|
||||
*/
|
||||
_setModuleDiscount(e) {
|
||||
Persist.setModuleDiscount(e.target.value * 1);
|
||||
_setModuleDiscount() {
|
||||
let moduleDiscount = normalizePercent(this.state.moduleDiscount);
|
||||
this.setState({ moduleDiscount });
|
||||
Persist.setModuleDiscount(moduleDiscount / 100); // Decimal value is stored
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the Ship discount
|
||||
*/
|
||||
_setShipDiscount() {
|
||||
let shipDiscount = normalizePercent(this.state.shipDiscount);
|
||||
this.setState({ shipDiscount });
|
||||
Persist.setShipDiscount(shipDiscount / 100); // Decimal value is stored
|
||||
}
|
||||
|
||||
/**
|
||||
* Input handler for the module discount field
|
||||
* @param {SyntheticEvent} e Event
|
||||
*/
|
||||
_changeModuleDiscount(e) {
|
||||
let moduleDiscount = e.target.value;
|
||||
|
||||
if (e.target.value === '' || e.target.value === '-' || e.target.value === '.') {
|
||||
this.setState({ moduleDiscount });
|
||||
} else if (!isNaN(moduleDiscount) && Math.round(moduleDiscount) < 100) {
|
||||
this.setState({ moduleDiscount });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Input handler for the ship discount field
|
||||
* @param {SyntheticEvent} e Event
|
||||
*/
|
||||
_setShipDiscount(e) {
|
||||
Persist.setShipDiscount(e.target.value * 1);
|
||||
_changeShipDiscount(e) {
|
||||
let shipDiscount = e.target.value;
|
||||
|
||||
if (e.target.value === '' || e.target.value === '-' || e.target.value === '.') {
|
||||
this.setState({ shipDiscount });
|
||||
} else if (!isNaN(shipDiscount) && Math.round(shipDiscount) < 100) {
|
||||
this.setState({ shipDiscount });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Key down/press handler for ship discount field
|
||||
* @param {SyntheticEvent} e Event
|
||||
*/
|
||||
_kpShipDiscount(e) {
|
||||
let sd = this.state.shipDiscount * 1;
|
||||
switch (e.keyCode) {
|
||||
case 38:
|
||||
e.preventDefault();
|
||||
this.setState({ shipDiscount: e.shiftKey ? nearestQtrPct(sd + 0.25) : normalizePercent(sd + 1) });
|
||||
break;
|
||||
case 40:
|
||||
e.preventDefault();
|
||||
this.setState({ shipDiscount: e.shiftKey ? nearestQtrPct(sd - 0.25) : normalizePercent(sd - 1) });
|
||||
break;
|
||||
case 13:
|
||||
e.preventDefault();
|
||||
e.target.blur();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Key down/press handler for module discount field
|
||||
* @param {SyntheticEvent} e Event
|
||||
*/
|
||||
_kpModuleDiscount(e) {
|
||||
let md = this.state.moduleDiscount * 1;
|
||||
switch (e.keyCode) {
|
||||
case 38:
|
||||
e.preventDefault();
|
||||
this.setState({ moduleDiscount: e.shiftKey ? nearestQtrPct(md + 0.25) : normalizePercent(md + 1) });
|
||||
break;
|
||||
case 40:
|
||||
e.preventDefault();
|
||||
this.setState({ moduleDiscount: e.shiftKey ? nearestQtrPct(md - 0.25) : normalizePercent(md - 1) });
|
||||
break;
|
||||
case 13:
|
||||
e.preventDefault();
|
||||
e.target.blur();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,14 +379,12 @@ export default class Header extends TranslatedComponent {
|
||||
</select>
|
||||
<br/>
|
||||
{translate('ship')} {translate('discount')}
|
||||
<select className='cap' value={Persist.getShipDiscount()} onChange={this._setShipDiscount}>
|
||||
{this.discountOptions}
|
||||
</select>
|
||||
<input type='text' size='10' value={this.state.shipDiscount} onChange={this._changeShipDiscount} onFocus={selectAll} onBlur={this._setShipDiscount} onKeyDown={this._kpShipDiscount}/>
|
||||
<u className='primary-disabled'>%</u>
|
||||
<br/>
|
||||
{translate('module')} {translate('discount')}
|
||||
<select className='cap' value={Persist.getModuleDiscount()} onChange={this._setModuleDiscount} >
|
||||
{this.discountOptions}
|
||||
</select>
|
||||
<input type='text' size='10' value={this.state.moduleDiscount} onChange={this._changeModuleDiscount} onFocus={selectAll} onBlur={this._setModuleDiscount} onKeyDown={this._kpModuleDiscount}/>
|
||||
<u className='primary-disabled'>%</u>
|
||||
</div>
|
||||
<hr />
|
||||
<ul>
|
||||
@@ -317,7 +420,7 @@ export default class Header extends TranslatedComponent {
|
||||
let update = () => this.forceUpdate();
|
||||
Persist.addListener('language', update);
|
||||
Persist.addListener('insurance', update);
|
||||
Persist.addListener('discounts', update);
|
||||
// Persist.addListener('discounts', update);
|
||||
Persist.addListener('deletedAll', update);
|
||||
Persist.addListener('builds', update);
|
||||
Persist.addListener('tooltips', update);
|
||||
@@ -336,6 +439,19 @@ export default class Header extends TranslatedComponent {
|
||||
this.insuranceOptions.push(<option key={name} value={name}>{translate(name)}</option>);
|
||||
}
|
||||
}
|
||||
if (nextProps.currentMenu == 'settings') { // Settings menu is about to be opened
|
||||
this.setState({
|
||||
shipDiscount: normalizePercent(Persist.getShipDiscount() * 100),
|
||||
moduleDiscount: normalizePercent(Persist.getModuleDiscount() * 100),
|
||||
});
|
||||
} else if (this.props.currentMenu == 'settings') { // Settings menu is about to be closed
|
||||
if (this.state.shipDiscount != (Persist.getShipDiscount() * 100)) {
|
||||
this._setShipDiscount();
|
||||
}
|
||||
if (this.state.moduleDiscount != (Persist.getModuleDiscount() * 100)) {
|
||||
this._setModuleDiscount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -96,7 +96,6 @@ export default class Ship {
|
||||
this.standard[3], // Add Life Support
|
||||
this.hardpoints
|
||||
);
|
||||
this.shipCostMultiplier = 1;
|
||||
this.moduleCostMultiplier = 1;
|
||||
this.priorityBands = [
|
||||
{ deployed: 0, retracted: 0, },
|
||||
@@ -345,11 +344,13 @@ export default class Ship {
|
||||
|
||||
/**
|
||||
* Recalculate all item costs and total based on discounts.
|
||||
* @param {Number} shipCostMultiplier Ship cost multiplier discount (e.g. 0.9 === 10% discount)
|
||||
* @param {Number} moduleCostMultiplier Module cost multiplier discount (e.g. 0.75 === 25% discount)
|
||||
* @param {Number} shipDiscount Ship cost discount (e.g. 0.1 === 10% discount)
|
||||
* @param {Number} moduleDiscount Module cost discount (e.g. 0.75 === 25% discount)
|
||||
* @return {this} The current ship instance for chaining
|
||||
*/
|
||||
applyDiscounts(shipCostMultiplier, moduleCostMultiplier) {
|
||||
applyDiscounts(shipDiscount, moduleDiscount) {
|
||||
let shipCostMultiplier = 1 - shipDiscount;
|
||||
let moduleCostMultiplier = 1 - moduleDiscount;
|
||||
let total = 0;
|
||||
let costList = this.costList;
|
||||
|
||||
@@ -362,7 +363,6 @@ export default class Ship {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.shipCostMultiplier = shipCostMultiplier;
|
||||
this.moduleCostMultiplier = moduleCostMultiplier;
|
||||
this.totalCost = total;
|
||||
return this;
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
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_INSURANCE = 'insurance';
|
||||
const LS_KEY_DISCOUNTS = 'discounts';
|
||||
const LS_KEY_SHIP_DISCOUNT = 'shipDiscount';
|
||||
const LS_KEY_MOD_DISCOUNT = 'moduleDiscount';
|
||||
const LS_KEY_STATE = 'state';
|
||||
const LS_KEY_SIZE_RATIO = 'sizeRatio';
|
||||
const LS_KEY_TOOLTIPS = 'tooltips';
|
||||
|
||||
let LS;
|
||||
|
||||
// Safe check to determine if localStorage is enabled
|
||||
try {
|
||||
localStorage.setItem('test', 'test');
|
||||
localStorage.removeItem('test');
|
||||
LS = localStorage;
|
||||
} catch(e) {
|
||||
LS = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe localstorage put
|
||||
* @param {String} key key
|
||||
@@ -48,7 +41,11 @@ function _getString(key) {
|
||||
*/
|
||||
function _get(key) {
|
||||
let str = _getString(key);
|
||||
return str ? JSON.parse(str) : null;
|
||||
try {
|
||||
return str ? JSON.parse(str) : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,18 +71,30 @@ export class Persist extends EventEmitter {
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
// Eventually check use session over localstorage - 'remember me' logic
|
||||
// Safe check to determine if localStorage is enabled
|
||||
try {
|
||||
localStorage.setItem('test', 'test');
|
||||
localStorage.removeItem('test');
|
||||
LS = localStorage;
|
||||
} catch(e) {
|
||||
LS = null;
|
||||
}
|
||||
|
||||
let tips = _get(LS_KEY_TOOLTIPS);
|
||||
let insurance = _getString(LS_KEY_INSURANCE);
|
||||
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);
|
||||
let tips = _get(LS_KEY_TOOLTIPS);
|
||||
let discounts = _get(LS_KEY_DISCOUNTS);
|
||||
|
||||
this.onStorageChange = this.onStorageChange.bind(this);
|
||||
this.langCode = _getString(LS_KEY_LANG) || 'en';
|
||||
this.insurance = _getString(LS_KEY_INSURANCE) || 'standard';
|
||||
this.discounts = discounts && !isNaN(discounts[0]) && !isNaN(discounts[1]) ? discounts : [1, 1];
|
||||
this.builds = buildJson ? buildJson : {};
|
||||
this.comparisons = comparisonJson ? comparisonJson : {};
|
||||
this.buildCount = Object.keys(this.builds).length;
|
||||
this.insurance = insurance && Insurance[insurance.toLowerCase()] !== undefined ? insurance : 'standard';
|
||||
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.state = _get(LS_KEY_STATE);
|
||||
this.sizeRatio = _get(LS_KEY_SIZE_RATIO) || 1;
|
||||
@@ -122,9 +131,13 @@ export class Persist extends EventEmitter {
|
||||
this.insurance = newValue;
|
||||
this.emit('insurance', newValue);
|
||||
break;
|
||||
case LS_KEY_DISCOUNTS:
|
||||
this.discounts = JSON.parse(newValue);
|
||||
this.emit('discounts', this.discounts);
|
||||
case LS_KEY_SHIP_DISCOUNT:
|
||||
this.shipDiscount = JSON.parse(newValue);
|
||||
this.emit('discounts');
|
||||
break;
|
||||
case LS_KEY_MOD_DISCOUNT:
|
||||
this.moduleDiscount = JSON.parse(newValue);
|
||||
this.emit('discounts');
|
||||
break;
|
||||
case LS_KEY_TOOLTIPS:
|
||||
this.tooltipsEnabled = !!newValue && newValue.toLowerCase() == 'true';
|
||||
@@ -361,7 +374,8 @@ export class Persist extends EventEmitter {
|
||||
data[LS_KEY_BUILDS] = this.getBuilds();
|
||||
data[LS_KEY_COMPARISONS] = this.getComparisons();
|
||||
data[LS_KEY_INSURANCE] = this.getInsurance();
|
||||
data[LS_KEY_DISCOUNTS] = this.discounts;
|
||||
data[LS_KEY_SHIP_DISCOUNT] = this.shipDiscount;
|
||||
data[LS_KEY_MOD_DISCOUNT] = this.moduleDiscount;
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -370,7 +384,7 @@ export class Persist extends EventEmitter {
|
||||
* @return {String} The name of the saved insurance type of null
|
||||
*/
|
||||
getInsurance() {
|
||||
return this.insurance;
|
||||
return this.insurance.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -388,8 +402,8 @@ export class Persist extends EventEmitter {
|
||||
* @param {number} shipDiscount Discount value/amount
|
||||
*/
|
||||
setShipDiscount(shipDiscount) {
|
||||
this.discounts[0] = shipDiscount;
|
||||
_put(LS_KEY_DISCOUNTS, this.discounts);
|
||||
this.shipDiscount = shipDiscount;
|
||||
_put(LS_KEY_SHIP_DISCOUNT, this.shipDiscount);
|
||||
this.emit('discounts');
|
||||
}
|
||||
|
||||
@@ -398,7 +412,7 @@ export class Persist extends EventEmitter {
|
||||
* @return {number} val Discount value/amount
|
||||
*/
|
||||
getShipDiscount() {
|
||||
return this.discounts[0];
|
||||
return this.shipDiscount;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -406,8 +420,8 @@ export class Persist extends EventEmitter {
|
||||
* @param {number} moduleDiscount Discount value/amount
|
||||
*/
|
||||
setModuleDiscount(moduleDiscount) {
|
||||
this.discounts[1] = moduleDiscount;
|
||||
_put(LS_KEY_DISCOUNTS, this.discounts);
|
||||
this.moduleDiscount = moduleDiscount;
|
||||
_put(LS_KEY_MOD_DISCOUNT, this.moduleDiscount);
|
||||
this.emit('discounts');
|
||||
}
|
||||
|
||||
@@ -416,7 +430,7 @@ export class Persist extends EventEmitter {
|
||||
* @return {number} val Discount value/amount
|
||||
*/
|
||||
getModuleDiscount() {
|
||||
return this.discounts[1];
|
||||
return this.moduleDiscount;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -95,6 +95,12 @@ header {
|
||||
background-color: @warning-disabled;
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
text-align: right;
|
||||
font-size: 1em;
|
||||
font-family: @fStandard;
|
||||
}
|
||||
|
||||
.smallTablet({
|
||||
max-height: 400px;
|
||||
|
||||
Reference in New Issue
Block a user