198 lines
6 KiB
JavaScript
198 lines
6 KiB
JavaScript
|
import React from 'react';
|
||
|
import ReactDOM from 'react-dom';
|
||
|
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||
|
import Flux from '../lib/flux/index';
|
||
|
import TutorialStore from '../stores/TutorialStore';
|
||
|
import WindowStore from '../stores/WindowStore';
|
||
|
import PopoutStore from '../stores/PopoutStore';
|
||
|
import '../styles/tutorial.styl';
|
||
|
import lodash from 'lodash';
|
||
|
import TutorialActionCreators from '../actions/TutorialActionCreators';
|
||
|
import TutorialData from '../data/tutorial_tips.json';
|
||
|
import Popout from './common/Popout';
|
||
|
import TutorialPopout from './TutorialPopout';
|
||
|
import classNames from 'classnames';
|
||
|
|
||
|
const TutorialIndicator = React.createClass({
|
||
|
mixins: [PureRenderMixin, Flux.StoreListenerMixin(PopoutStore)],
|
||
|
|
||
|
propTypes: {
|
||
|
position: React.PropTypes.oneOf(['left', 'top', 'right', 'bottom']).isRequired,
|
||
|
x: React.PropTypes.number.isRequired,
|
||
|
y: React.PropTypes.number.isRequired,
|
||
|
targetWidth: React.PropTypes.number.isRequired,
|
||
|
targetHeight: React.PropTypes.number.isRequired,
|
||
|
tutorialId: React.PropTypes.string.isRequired,
|
||
|
offset: React.PropTypes.shape({x: React.PropTypes.number, y: React.PropTypes.number}).isRequired,
|
||
|
offsetPercent: React.PropTypes.shape({x: React.PropTypes.number, y: React.PropTypes.number}).isRequired,
|
||
|
},
|
||
|
|
||
|
getInitialState() {
|
||
|
return {
|
||
|
offsetX: null,
|
||
|
offsetY: null,
|
||
|
definition: TutorialData[this.props.tutorialId],
|
||
|
};
|
||
|
},
|
||
|
|
||
|
getStateFromStores() {
|
||
|
return {
|
||
|
isPopoutOpen: PopoutStore.isOpen(this.props.tutorialId),
|
||
|
};
|
||
|
},
|
||
|
|
||
|
componentDidMount() {
|
||
|
this.updateCache();
|
||
|
},
|
||
|
|
||
|
componentDidUpdate({tutorialId, x, y, targetWidth, targetHeight, position, offset, offsetPercent}) {
|
||
|
if (
|
||
|
this.props.tutorialId != tutorialId ||
|
||
|
this.props.x !== x ||
|
||
|
this.props.y !== y ||
|
||
|
this.props.targetWidth !== targetWidth ||
|
||
|
this.props.targetHeight !== targetHeight ||
|
||
|
this.props.position !== position ||
|
||
|
this.props.offset.x !== offset.x ||
|
||
|
this.props.offset.y !== offset.y ||
|
||
|
this.props.offsetPercent.x !== offsetPercent.x ||
|
||
|
this.props.offsetPercent.y !== offsetPercent.y
|
||
|
) {
|
||
|
this.updateCache();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
updateCache() {
|
||
|
const newState = {
|
||
|
offsetX: (this.props.targetWidth - ReactDOM.findDOMNode(this).offsetWidth) / 2,
|
||
|
offsetY: (this.props.targetHeight - ReactDOM.findDOMNode(this).offsetHeight) / 2,
|
||
|
};
|
||
|
|
||
|
const position = this.props.position;
|
||
|
|
||
|
switch (position) {
|
||
|
case 'left':
|
||
|
newState.offsetX = 0;
|
||
|
newState.offsetY = this.props.targetHeight / 2;
|
||
|
break;
|
||
|
case 'right':
|
||
|
newState.offsetX = this.props.targetWidth;
|
||
|
newState.offsetY = this.props.targetHeight / 2;
|
||
|
break;
|
||
|
case 'bottom':
|
||
|
newState.offsetX = this.props.targetWidth / 2;
|
||
|
newState.offsetY = this.props.targetHeight;
|
||
|
break;
|
||
|
case 'top':
|
||
|
default:
|
||
|
newState.offsetX = this.props.targetWidth / 2;
|
||
|
newState.offsetY = 0;
|
||
|
}
|
||
|
|
||
|
const sanititzedOffset = {
|
||
|
x: this.props.offset.x ? this.props.offset.x : 0,
|
||
|
y: this.props.offset.y ? this.props.offset.y : 0,
|
||
|
};
|
||
|
const sanititzedOffsetPercent = {
|
||
|
x: this.props.offsetPercent.x ? this.props.offsetPercent.x : 0,
|
||
|
y: this.props.offsetPercent.y ? this.props.offsetPercent.y : 0,
|
||
|
};
|
||
|
|
||
|
newState.offsetX += sanititzedOffset.x + sanititzedOffsetPercent.x / 100 * this.props.targetWidth;
|
||
|
newState.offsetY += sanititzedOffset.y + sanititzedOffsetPercent.y / 100 * this.props.targetHeight;
|
||
|
|
||
|
this.setState(newState);
|
||
|
},
|
||
|
|
||
|
handleSkipTips() {
|
||
|
TutorialActionCreators.suppressAll();
|
||
|
},
|
||
|
|
||
|
handlePopoutClose(wasConfirmed) {
|
||
|
if (wasConfirmed) {
|
||
|
TutorialActionCreators.dismiss(this.props.tutorialId);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
renderTutorialPopout(props) {
|
||
|
return (
|
||
|
<TutorialPopout
|
||
|
{...props}
|
||
|
tutorialId={this.props.tutorialId}
|
||
|
onSkipAll={this.handleSkipTips}
|
||
|
onClose={this.handlePopoutClose}
|
||
|
/>
|
||
|
);
|
||
|
},
|
||
|
|
||
|
render() {
|
||
|
const style = {
|
||
|
left: this.state.offsetX === null ? null : this.props.x + this.state.offsetX,
|
||
|
top: this.state.offsetY === null ? null : this.props.y + this.state.offsetY,
|
||
|
};
|
||
|
|
||
|
const shouldRender = this.props.isFocused && !this.state.isPopoutOpen;
|
||
|
|
||
|
const exclamationMark = this.state.definition.highPriority !== true
|
||
|
? null
|
||
|
: <div>
|
||
|
<div className={classNames('top', {animating: shouldRender, 'not-animating': !shouldRender})} />
|
||
|
<div className={classNames('bottom', {animating: shouldRender, 'not-animating': !shouldRender})} />
|
||
|
</div>;
|
||
|
|
||
|
const styleClasses = {
|
||
|
animating: shouldRender,
|
||
|
'not-animating': !shouldRender,
|
||
|
highPriority: this.state.definition.highPriority,
|
||
|
};
|
||
|
|
||
|
return (
|
||
|
<Popout
|
||
|
render={this.renderTutorialPopout}
|
||
|
position={this.state.definition.popoutPosition}
|
||
|
backdrop={true}
|
||
|
uniqueId={this.props.tutorialId}>
|
||
|
<div className="indicator" style={style}>
|
||
|
<div className={classNames('animation-container', styleClasses)}>
|
||
|
{exclamationMark}
|
||
|
<div className={classNames('inner-circle', styleClasses)} />
|
||
|
<div className={classNames('outer-circle', styleClasses)} />
|
||
|
</div>
|
||
|
</div>
|
||
|
</Popout>
|
||
|
);
|
||
|
},
|
||
|
});
|
||
|
|
||
|
const TutorialIndicators = React.createClass({
|
||
|
mixins: [Flux.StoreListenerMixin(TutorialStore, WindowStore)],
|
||
|
|
||
|
getStateFromStores() {
|
||
|
return {
|
||
|
indicators: TutorialStore.getIndicators(),
|
||
|
shouldShowAny: TutorialStore.shouldShowAnyIndicators(),
|
||
|
isFocused: WindowStore.isFocused(),
|
||
|
};
|
||
|
},
|
||
|
|
||
|
render() {
|
||
|
let indicators = null;
|
||
|
|
||
|
if (this.state.shouldShowAny) {
|
||
|
indicators = lodash.map(this.state.indicators, (props, tutorialId) => {
|
||
|
return (
|
||
|
<TutorialIndicator key={tutorialId} tutorialId={tutorialId} isFocused={this.state.isFocused} {...props} />
|
||
|
);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return <div className="tutorial-indicators">{indicators}</div>;
|
||
|
},
|
||
|
});
|
||
|
|
||
|
export default TutorialIndicators;
|
||
|
|
||
|
|
||
|
|
||
|
// WEBPACK FOOTER //
|
||
|
// ./discord_app/components/TutorialIndicators.js
|