import React from 'react'; import ReactDOM from 'react-dom'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { bindActionCreators } from 'redux'; import * as actions from 'loot-core/src/client/actions'; import BudgetCategories from './tutorial/BudgetCategories'; import BudgetInitial from './tutorial/BudgetInitial'; import BudgetNewIncome from './tutorial/BudgetNewIncome'; import BudgetNextMonth from './tutorial/BudgetNextMonth'; import BudgetSummary from './tutorial/BudgetSummary'; import CategoryBalance from './tutorial/CategoryBalance'; import Final from './tutorial/Final'; import Intro from './tutorial/Intro'; import Overspending from './tutorial/Overspending'; import TransactionAdd from './tutorial/TransactionAdd'; import TransactionEnter from './tutorial/TransactionEnter'; function generatePath(innerRect, outerRect) { const i = innerRect; const o = outerRect; // prettier-ignore return ` M0,0 ${o.width},0 ${o.width},${o.height} L0,${o.height} L0,0 Z M${i.left},${i.top} L${i.left+i.width},${i.top} L${i.left+i.width},${i.top+i.height} L${i.left},${i.top+i.height} L${i.left},${i.top} Z `; } function expandRect({ top, left, width, height }, padding) { if (typeof padding === 'number') { return { top: top - padding, left: left - padding, width: width + padding * 2, height: height + padding * 2 }; } else if (padding) { return { top: top - (padding.top || 0), left: left - (padding.left || 0), width: width + (padding.right || 0) + (padding.left || 0), height: height + (padding.bottom || 0) + (padding.top || 0) }; } return { top, left, width, height }; } function withinWindow(rect) { return { top: rect.top, left: rect.left, width: Math.min(rect.left + rect.width, window.innerWidth) - rect.left, height: Math.min(rect.top + rect.height, window.innerHeight) - rect.top }; } class MeasureNodes extends React.Component { state = { measurements: null }; componentDidMount() { window.addEventListener('resize', () => { setTimeout(() => this.updateMeasurements(true), 0); }); this.updateMeasurements(); } componentDidUpdate(prevProps) { if (prevProps.nodes !== this.props.nodes) { this.updateMeasurements(); } } updateMeasurements() { this.setState({ measurements: this.props.nodes.map(node => node.getBoundingClientRect()) }); } render() { const { children } = this.props; const { measurements } = this.state; return measurements ? children(...measurements) : null; } } class Tutorial extends React.Component { state = { highlightRect: null, windowRect: null }; static contextTypes = { getTutorialNode: PropTypes.func, endTutorial: PropTypes.func }; onClose = didQuitEarly => { // The difference between these is `endTutorial` permanently // disable the tutorial. If the user walked all the way through // it, never show it to them again. Otherwise they will see if // again if they create a new budget. if (didQuitEarly) { this.props.closeTutorial(); } else { this.props.endTutorial(); } }; getContent(stage, targetRect, navigationProps) { switch (stage) { case 'budget-summary': return ( ); case 'budget-categories': return ( ); case 'transaction-add': return ( ); case 'budget-new-income': return ( ); case 'budget-next-month': return
hi
; } } render() { const { stage, fromYNAB, nextTutorialStage, closeTutorial } = this.props; if (stage === null) { return null; } const navigationProps = { nextTutorialStage: this.props.nextTutorialStage, previousTutorialStage: this.props.previousTutorialStage, closeTutorial: () => this.onClose(true), endTutorial: () => this.onClose(false) }; switch (stage) { case 'intro': return ( ); case 'budget-initial': return ( ); case 'budget-next-month': return ( ); case 'budget-next-month2': return ( ); case 'transaction-enter': return ( ); case 'budget-category-balance': return ; case 'budget-overspending': return ; case 'budget-overspending2': return ( ); case 'final': return ( ); } const { node: targetNode, expand } = this.context.getTutorialNode(stage); return ( {(targetRect, windowRect) => { targetRect = withinWindow( expandRect(expandRect(targetRect, 5), expand) ); return (
{ReactDOM.createPortal( , document.body )} {this.getContent(stage, targetRect, navigationProps)}
); }}
); } } export default connect( state => ({ stage: state.tutorial.stage, fromYNAB: state.tutorial.fromYNAB }), dispatch => bindActionCreators(actions, dispatch) )(Tutorial);