import React from 'react'; import TranslatedComponent from './TranslatedComponent'; import { Languages } from '../i18n/Language'; import { Insurance } from '../shipyard/Constants'; import Link from './Link'; import ActiveLink from './ActiveLink'; import cn from 'classnames'; import { Cogs, CoriolisLogo, Hammer, Help, Rocket, StatsBars } from './SvgIcons'; import Persist from '../stores/Persist'; import ModalDeleteAll from './ModalDeleteAll'; import ModalExport from './ModalExport'; import ModalHelp from './ModalHelp'; import ModalImport from './ModalImport'; import Slider from './Slider'; import Announcement from './Announcement'; import { outfitURL } from '../utils/UrlGenerators'; import autoBind from 'auto-bind'; import { Factory, Ship } from 'ed-forge'; import { chain, entries } from 'lodash'; 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 * 1000) / 1000; 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 */ export default class Header extends TranslatedComponent { /** * Constructor * @param {Object} props React Component properties * @param {Object} context React Component context */ constructor(props, context) { super(props); autoBind(this); this.ships = Factory.getAllShipTypes().sort(); this._openShips = this._openMenu.bind(this, 's'); this._openBuilds = this._openMenu.bind(this, 'b'); this._openComp = this._openMenu.bind(this, 'comp'); this._openAnnounce = this._openMenu.bind(this, 'announce'); this._openSettings = this._openMenu.bind(this, 'settings'); this.languageOptions = []; this.insuranceOptions = []; this.state = { shipDiscount: normalizePercent(Persist.getShipDiscount() * 100), moduleDiscount: normalizePercent(Persist.getModuleDiscount() * 100), }; let translate = context.language.translate; for (let langCode in Languages) { this.languageOptions.push(); } for (let name in Insurance) { this.insuranceOptions.push(); } } /** * Update insurance level * @param {SyntheticEvent} e Event */ _setInsurance(e) { Persist.setInsurance(e.target.value); } /** * Update the Module discount */ _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 */ _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(); } } /** * Update the current language * @param {SyntheticEvent} e Event */ _setLanguage(e) { Persist.setLangCode(e.target.value); } /** * Toggle tooltips setting */ _toggleTooltips() { Persist.showTooltips(!Persist.showTooltips()); } /** * Show delete all modal * @param {SyntheticEvent} e Event */ _showDeleteAll(e) { e.preventDefault(); this.context.showModal(); }; /** * Show export modal with detailed export * @param {SyntheticEvent} e Event */ _showDetailedExport(e) { let translate = this.context.language.translate; e.preventDefault(); const builds = chain(Persist.getBuilds()) .values() .map((builds) => Object.values(builds)) .flatMap() .map((code) => new Ship(code)) .value(); this.context.showModal( { return { header: { appName: 'Inara', 'appVersion': '1.0' }, data: build.toJSON(), }; }))} />); } /** * Show help modal * @param {SyntheticEvent} e Event */ _showHelp(e) { let translate = this.context.language.translate; e.preventDefault(); this.context.showModal(); } /** * Show import modal * @param {SyntheticEvent} e Event */ _showImport(e) { e.preventDefault(); this.context.showModal(); } /** * Update the app scale / size ratio * @param {number} scale scale Size Ratio */ _setTextSize(scale) { Persist.setSizeRatio((scale * SIZE_RANGE) + SIZE_MIN); } /** * Reset the app scale / size ratio */ _resetTextSize() { Persist.setSizeRatio(1); } /** * Open a menu * @param {string} menu Menu name * @param {SyntheticEvent} event Event */ _openMenu(menu, event) { event.stopPropagation(); if (this.props.currentMenu == menu) { menu = null; } this.context.openMenu(menu); } /** * Generate the ships menu * @return {React.Component} Menu */ _getShipsMenu() { const { translate } = this.context.language; return (
e.stopPropagation() }> {this.ships.map((s) => {translate(s)})}
); } /** * Generate the builds menu * @return {React.Component} Menu */ _getBuildsMenu() { const { translate } = this.context.language; let builds = Persist.getBuilds(); let buildList = []; for (let shipId of this.ships) { if (builds[shipId]) { let shipBuilds = []; let buildNameOrder = Object.keys(builds[shipId]).sort(); for (let buildName of buildNameOrder) { let href = outfitURL(shipId, builds[shipId][buildName], buildName); shipBuilds.push(
  • {buildName}
  • ); } buildList.push(
      {translate(shipId)}{shipBuilds}
    ); } } return (
    e.stopPropagation() }>
    {buildList}
    ); } /** * Generate the comparison menu * @return {React.Component} Menu */ _getComparisonsMenu() { let comparisons; let translate = this.context.language.translate; if (Persist.hasComparisons()) { comparisons = []; let comps = Object.keys(Persist.getComparisons()).sort(); for (let name of comps) { comparisons.push({name}); } } else { comparisons = {translate('none created')}; } return (
    e.stopPropagation() } style={{ whiteSpace: 'nowrap' }}> {comparisons}
    {translate('compare all')} {translate('create new')}
    ); } /** * Generate the announcement menu * @return {React.Component} Menu */ _getAnnouncementsMenu() { let announcements; let translate = this.context.language.translate; if (this.props.announcements) { announcements = []; for (let announce of this.props.announcements) { if (announce.expiry < Date.now()) { continue; } announcements.push(); announcements.push(
    ); } } return (
    e.stopPropagation() } style={{ whiteSpace: 'nowrap' }}> {announcements}
    ); } /** * Generate the settings menu * @return {React.Component} Menu */ _getSettingsMenu() { let translate = this.context.language.translate; let tips = Persist.showTooltips(); return (
    e.stopPropagation() }>
    {translate('language')}
    {translate('tooltips')} {(tips ? '✓' : '✗')}
    {translate('insurance')}
    {translate('ship')} {translate('discount')} %
    {translate('module')} {translate('discount')} %

      {translate('builds')} & {translate('comparisons')}
    • {translate('detailed export')}
    • {translate('import')}
    • {translate('delete all')}

    A A
    {translate('reset')}

    {translate('about')}
    ); } /** * Add listeners on mount */ componentWillMount() { let update = () => this.forceUpdate(); Persist.addListener('language', update); Persist.addListener('insurance', update); // Persist.addListener('discounts', update); Persist.addListener('deletedAll', update); Persist.addListener('builds', update); Persist.addListener('tooltips', update); } /** * Update state based on property and context changes * @param {Object} nextProps Incoming/Next properties * @param {Object} nextContext Incoming/Next conext */ componentWillReceiveProps(nextProps, nextContext) { if(this.context.language != nextContext.language) { let translate = nextContext.language.translate; this.insuranceOptions = []; for (let name in Insurance) { this.insuranceOptions.push(); } } 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(); } } } async update() { const reg = await navigator.serviceWorker.getRegistration(); if (!reg || !reg.waiting) { return window.location.reload(); } reg.waiting.postMessage('skipWaiting'); window.location.reload(); } /** * Render the header * @return {React.Component} Header */ render() { let translate = this.context.language.translate; let openedMenu = this.props.currentMenu; let hasBuilds = Persist.hasBuilds(); return (
    {this.props.appCacheUpdate &&
    {translate('PHRASE_UPDATE_RDY')}
    } {this.props.appCacheUpdate ? {'View Release Changes'} : null}
    {translate('ships')}
    {openedMenu == 's' ? this._getShipsMenu() : null}
    {translate('builds')}
    {openedMenu == 'b' ? this._getBuildsMenu() : null}
    {/* TODO: Enable */} {/*
    {translate('announcements')}
    {openedMenu == 'announce' ? this._getAnnouncementsMenu() : null}
    */} {window.location.origin.search('.edcd.io') >= 0 ? : null }
    {translate('settings')}
    {openedMenu == 'settings' ? this._getSettingsMenu() : null}
    ); } }