260 lines
6.9 KiB
JavaScript
Executable file
260 lines
6.9 KiB
JavaScript
Executable file
/* @flow */
|
|
|
|
import React from 'react';
|
|
import lodash from 'lodash';
|
|
import classNames from 'classnames';
|
|
import Flux from '../lib/flux';
|
|
import Store from '../lib/flux/Store';
|
|
import Animated from '../lib/animated';
|
|
import Keybinds from '../lib/Keybinds';
|
|
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
|
import TransitionGroup from '../lib/transitions/TransitionGroup';
|
|
import {ComponentDispatch} from '../utils/ComponentDispatchUtils';
|
|
import UserSettings from './user_settings/UserSettings';
|
|
import ChannelSettings from './channel_settings/ChannelSettings';
|
|
import GuildSettings from './guild_settings/GuildSettings';
|
|
import Verification from './verification/Verification';
|
|
import LayerStore from '../stores/LayerStore';
|
|
import {SETTINGS_LAYERS_LAYOUT} from '../keybinds/settingsLayers';
|
|
import {popLayer} from '../actions/LayerActionCreators';
|
|
import {Layers as LayerKeys, ComponentActions} from '../Constants';
|
|
import '../styles/layers.styl';
|
|
|
|
const LayerComponents: {[key: string]: typeof ReactComponent} = {
|
|
[LayerKeys.USER_SETTINGS]: UserSettings,
|
|
[LayerKeys.CHANNEL_SETTINGS]: ChannelSettings,
|
|
[LayerKeys.GUILD_SETTINGS]: GuildSettings,
|
|
[LayerKeys.VERIFICATION]: Verification,
|
|
};
|
|
|
|
const MODE_SHOWN = 'SHOWN';
|
|
const MODE_HIDDEN = 'HIDDEN';
|
|
|
|
const SCALE_MIN = 0.93;
|
|
const SCALE_DEFAULT = 1;
|
|
const SCALE_MAX = 1.1;
|
|
const OPACITY_HIDDEN = 0;
|
|
const OPACITY_SHOWN = 1;
|
|
const SPRING_SETTINGS = {
|
|
friction: 10,
|
|
tension: 100,
|
|
};
|
|
|
|
type LayerState = {
|
|
animating: boolean,
|
|
scale: Animated.Value,
|
|
opacity: Animated.Value,
|
|
};
|
|
|
|
class Layer extends React.PureComponent {
|
|
state: LayerState;
|
|
_ref: any;
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
let initialScale = SCALE_DEFAULT;
|
|
let initialOpacity = OPACITY_SHOWN;
|
|
if (props.mode === MODE_HIDDEN) {
|
|
initialScale = SCALE_MIN;
|
|
initialOpacity = OPACITY_HIDDEN;
|
|
}
|
|
this.state = {
|
|
animating: false,
|
|
scale: new Animated.Value(initialScale),
|
|
opacity: new Animated.Value(initialOpacity),
|
|
};
|
|
lodash.bindAll(this, ['setRef']);
|
|
}
|
|
|
|
setRef(ref) {
|
|
this._ref = ref;
|
|
}
|
|
|
|
getRef() {
|
|
return this._ref || {};
|
|
}
|
|
|
|
componentWillReceiveProps(nextProps) {
|
|
const {mode: prevMode} = this.props;
|
|
const {mode} = nextProps;
|
|
if ((mode === MODE_SHOWN && prevMode === MODE_HIDDEN) || (mode === MODE_HIDDEN && prevMode === MODE_SHOWN)) {
|
|
this.setState({animating: true});
|
|
}
|
|
}
|
|
|
|
componentDidUpdate(prevProps) {
|
|
const {mode} = this.props;
|
|
const {mode: prevMode} = prevProps;
|
|
if (mode === prevMode) {
|
|
return;
|
|
}
|
|
if (mode === MODE_SHOWN && prevMode === MODE_HIDDEN) {
|
|
return this.animateIn();
|
|
}
|
|
if (mode === MODE_HIDDEN && prevMode === MODE_SHOWN) {
|
|
return this.animateUnder();
|
|
}
|
|
}
|
|
|
|
componentWillEnter(callback) {
|
|
// The default animation state for layers that have been added
|
|
const {opacity, scale} = this.state;
|
|
scale.setValue(SCALE_MAX);
|
|
opacity.setValue(OPACITY_HIDDEN);
|
|
this.setState({animating: true}, () => this.animateIn(callback));
|
|
}
|
|
|
|
componentWillLeave(callback) {
|
|
this.setState({animating: true}, () => this.animateOut(callback));
|
|
}
|
|
|
|
animateIn(callback) {
|
|
Store.pauseEmittingChanges(500);
|
|
const {opacity, scale} = this.state;
|
|
Animated.parallel([
|
|
Animated.spring(opacity, {
|
|
toValue: OPACITY_SHOWN,
|
|
...SPRING_SETTINGS,
|
|
}),
|
|
Animated.spring(scale, {
|
|
toValue: SCALE_DEFAULT,
|
|
...SPRING_SETTINGS,
|
|
}),
|
|
]).start(() => this.animateComplete(callback));
|
|
}
|
|
|
|
animateOut(callback) {
|
|
Store.pauseEmittingChanges(500);
|
|
const {opacity, scale} = this.state;
|
|
const {onPop} = this.getRef().constructor;
|
|
Animated.parallel([
|
|
Animated.spring(opacity, {
|
|
toValue: OPACITY_HIDDEN,
|
|
...SPRING_SETTINGS,
|
|
}),
|
|
Animated.spring(scale, {
|
|
toValue: SCALE_MAX,
|
|
...SPRING_SETTINGS,
|
|
}),
|
|
]).start(() => {
|
|
callback();
|
|
onPop && onPop();
|
|
ComponentDispatch.dispatch(ComponentActions.LAYER_POP_COMPLETE);
|
|
});
|
|
}
|
|
|
|
animateUnder() {
|
|
Store.pauseEmittingChanges(500);
|
|
const {opacity, scale} = this.state;
|
|
Animated.parallel([
|
|
Animated.spring(opacity, {
|
|
toValue: OPACITY_HIDDEN,
|
|
...SPRING_SETTINGS,
|
|
}),
|
|
Animated.spring(scale, {
|
|
toValue: SCALE_MIN,
|
|
...SPRING_SETTINGS,
|
|
}),
|
|
]).start(() => this.animateComplete());
|
|
}
|
|
|
|
animateComplete(callback) {
|
|
this.setState({animating: false}, callback);
|
|
}
|
|
|
|
render() {
|
|
const {animating} = this.state;
|
|
const {mode} = this.props;
|
|
const style = animating || mode === MODE_HIDDEN ? this.getAnimatedStyle() : null;
|
|
const children = React.cloneElement(React.Children.only(this.props.children), {ref: this.setRef});
|
|
return (
|
|
<Animated.div className={classNames('layer', {animating})} style={style}>
|
|
{children}
|
|
</Animated.div>
|
|
);
|
|
}
|
|
|
|
getAnimatedStyle() {
|
|
const {opacity, scale} = this.state;
|
|
return {
|
|
opacity: opacity,
|
|
transform: [{scale}, {translateZ: 0}],
|
|
};
|
|
}
|
|
}
|
|
|
|
const Layers = React.createClass({
|
|
mixins: [PureRenderMixin, Flux.LazyStoreListenerMixin(LayerStore)],
|
|
|
|
getInitialState() {
|
|
return this.getStateFromStores();
|
|
},
|
|
|
|
getStateFromStores() {
|
|
return {
|
|
layers: LayerStore.getLayers(),
|
|
};
|
|
},
|
|
|
|
componentDidMount() {
|
|
ComponentDispatch.subscribe(ComponentActions.LAYER_POP_ESCAPE_KEY, popLayer);
|
|
},
|
|
|
|
componentWillUpdate(nextProps, nextState) {
|
|
const previousHasLayers = this.state.layers.length > 0;
|
|
const nextHasLayers = nextState.layers.length > 0;
|
|
|
|
if (!previousHasLayers && nextHasLayers) {
|
|
Keybinds.disable();
|
|
if (nextState.layers[nextState.layers.length - 1] !== LayerKeys.VERIFICATION) {
|
|
Keybinds.enableTemp(SETTINGS_LAYERS_LAYOUT);
|
|
}
|
|
} else if (previousHasLayers && !nextHasLayers) {
|
|
Keybinds.disableTemp();
|
|
}
|
|
},
|
|
|
|
componentWillUnmount() {
|
|
ComponentDispatch.unsubscribe(ComponentActions.LAYER_POP_ESCAPE_KEY, popLayer);
|
|
},
|
|
|
|
renderLayers() {
|
|
const {children} = this.props;
|
|
const {layers} = this.state;
|
|
const {length} = layers;
|
|
const components = [];
|
|
components.push(
|
|
<Layer key="layer-base" mode={length === 0 ? MODE_SHOWN : MODE_HIDDEN}>
|
|
{children}
|
|
</Layer>
|
|
);
|
|
layers.forEach((component, i) => components.push(this.renderComponent(component, i, length)));
|
|
return components;
|
|
},
|
|
|
|
renderComponent(Component: Function | string, key: number, length: number) {
|
|
if (typeof Component === 'string') {
|
|
Component = LayerComponents[Component];
|
|
}
|
|
return (
|
|
<Layer key={`layer-${key}`} mode={key === length - 1 ? MODE_SHOWN : MODE_HIDDEN}>
|
|
<Component />
|
|
</Layer>
|
|
);
|
|
},
|
|
|
|
render() {
|
|
return (
|
|
<TransitionGroup component="div" className={classNames('layers', this.props.className)}>
|
|
{this.renderLayers()}
|
|
</TransitionGroup>
|
|
);
|
|
},
|
|
});
|
|
|
|
export default Layers;
|
|
|
|
|
|
|
|
// WEBPACK FOOTER //
|
|
// ./discord_app/components/Layers.js
|