351 lines
10 KiB
JavaScript
351 lines
10 KiB
JavaScript
|
import React from 'react';
|
||
|
import Flux from '../../lib/flux';
|
||
|
import lodash from 'lodash';
|
||
|
import Popout from '../common/Popout';
|
||
|
import SwitchItem from '../../uikit/SwitchItem';
|
||
|
import Select from '../../uikit/Select';
|
||
|
import OverlayIconOff from '../../uikit/icons/OverlayIconOff';
|
||
|
import OverlayIconOn from '../../uikit/icons/OverlayIconOn';
|
||
|
import Button, {ButtonSizes, ButtonLooks, ButtonColors} from '../../uikit/Button';
|
||
|
import {FormSection, FormTitleTags, FormTitle, FormDivider} from '../../uikit/form';
|
||
|
import HoverRoll from '../../uikit/HoverRoll';
|
||
|
import EmptyState, {EmptyStateText, EmptyStateImage} from '../../uikit/EmptyState';
|
||
|
import classNames from 'classnames';
|
||
|
import NativeUtils from '../../utils/NativeUtils';
|
||
|
import UserSettingsActionCreators from '../../actions/UserSettingsActionCreators';
|
||
|
import UserSettingsStore from '../../stores/UserSettingsStore';
|
||
|
import RunningGameStore from '../../stores/RunningGameStore';
|
||
|
import GamesActionCreators from '../../actions/GamesActionCreators';
|
||
|
import Tooltip from '../common/Tooltip';
|
||
|
import i18n from '../../i18n';
|
||
|
import '../../styles/user_settings_games.styl';
|
||
|
import '../../styles/round_remove_button.styl';
|
||
|
import '../../styles/shared/hover_card.styl';
|
||
|
import type {RunningGame} from '../../flow/Client';
|
||
|
|
||
|
const ThemedEmptyState = Flux.connectStores([UserSettingsStore], () => ({
|
||
|
theme: UserSettingsStore.theme,
|
||
|
}))(EmptyState);
|
||
|
|
||
|
const enableOverlayToggle = NativeUtils.isWindows();
|
||
|
|
||
|
class AddGamePopout extends React.PureComponent {
|
||
|
constructor(props) {
|
||
|
super(props);
|
||
|
|
||
|
this.state = {
|
||
|
selectedValue: null,
|
||
|
};
|
||
|
|
||
|
lodash.bindAll(this, ['handleGameChange', 'handleAdd', 'handleClose']);
|
||
|
}
|
||
|
|
||
|
render() {
|
||
|
const {candidates} = this.props;
|
||
|
const {selectedValue} = this.state;
|
||
|
|
||
|
const options = candidates.map(candidate => ({value: candidate.pid, label: candidate.name}));
|
||
|
|
||
|
return (
|
||
|
<div className="add-game-popout elevation-border-high">
|
||
|
<Select clearable={false} options={options} onChange={this.handleGameChange} value={selectedValue} />
|
||
|
<FormDivider className="margin-top-8 margin-bottom-8" />
|
||
|
<div className="actions flex-horizontal">
|
||
|
<Button
|
||
|
className="cancel-button"
|
||
|
look={ButtonLooks.LINK}
|
||
|
color={ButtonColors.GREY}
|
||
|
onClick={this.handleClose}>
|
||
|
{i18n.Messages.CANCEL}
|
||
|
</Button>
|
||
|
<Button disabled={selectedValue == null} onClick={this.handleAdd}>
|
||
|
{i18n.Messages.SETTINGS_GAMES_ADD_NEW_GAME}
|
||
|
</Button>
|
||
|
</div>
|
||
|
</div>
|
||
|
);
|
||
|
}
|
||
|
|
||
|
handleGameChange(value) {
|
||
|
this.setState({selectedValue: value});
|
||
|
}
|
||
|
|
||
|
handleAdd() {
|
||
|
GamesActionCreators.addGame(this.state.selectedValue.value);
|
||
|
const {onClose} = this.props;
|
||
|
onClose();
|
||
|
}
|
||
|
|
||
|
handleClose() {
|
||
|
const {onClose} = this.props;
|
||
|
onClose();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const AddGamePopoutConnected = Flux.connectStores([RunningGameStore], () => {
|
||
|
return {
|
||
|
candidates: RunningGameStore.getCandidateGames(),
|
||
|
};
|
||
|
})(AddGamePopout);
|
||
|
|
||
|
class Game extends React.PureComponent {
|
||
|
constructor(props) {
|
||
|
super(props);
|
||
|
|
||
|
lodash.bindAll(this, [
|
||
|
'handleRemoveClick',
|
||
|
'handleNameEdit',
|
||
|
'handleNameChange',
|
||
|
'handleOverlayToggle',
|
||
|
'handleEnterKey',
|
||
|
]);
|
||
|
|
||
|
this.state = {
|
||
|
editingName: props.game != null ? props.game.gameName : '',
|
||
|
};
|
||
|
}
|
||
|
|
||
|
componentWillReceiveProps(newProps) {
|
||
|
if (this.props.nowPlaying && newProps.game) {
|
||
|
if (newProps.game.gameName !== this.state.editingName) {
|
||
|
this.setState({editingName: newProps.game.gameName});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
renderName() {
|
||
|
const {game} = this.props;
|
||
|
|
||
|
if (game == null) {
|
||
|
return <div className="game-name">{i18n.Messages.SETTINGS_GAMES_NO_GAME_DETECTED}</div>;
|
||
|
}
|
||
|
|
||
|
if (game.verified) {
|
||
|
return (
|
||
|
<div className="flex-horizontal">
|
||
|
<div className="game-name game-name-verified">
|
||
|
{game.gameName}
|
||
|
</div>
|
||
|
|
||
|
<Tooltip text={i18n.Messages.SETTINGS_GAMES_VERIFIED_ICON}>
|
||
|
<i className="game-verified-icon" />
|
||
|
</Tooltip>
|
||
|
</div>
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return (
|
||
|
<input
|
||
|
className="game-name game-name-input"
|
||
|
type="text"
|
||
|
maxLength={128}
|
||
|
value={this.state.editingName}
|
||
|
onBlur={this.handleNameEdit}
|
||
|
onKeyDown={this.handleEnterKey}
|
||
|
onChange={this.handleNameChange}
|
||
|
/>
|
||
|
);
|
||
|
}
|
||
|
|
||
|
renderLastPlayed() {
|
||
|
const {game, nowPlaying} = this.props;
|
||
|
|
||
|
if (game == null) {
|
||
|
return <div className="last-played">{i18n.Messages.SETTINGS_GAMES_NOT_PLAYING}</div>;
|
||
|
}
|
||
|
|
||
|
const {played, exePath} = game;
|
||
|
let lastPlayed;
|
||
|
|
||
|
if (nowPlaying) {
|
||
|
lastPlayed = i18n.Messages.SETTINGS_GAMES_NOW_PLAYING_STATE;
|
||
|
} else if (played) {
|
||
|
lastPlayed = i18n.Messages.SETTINGS_GAMES_LAST_PLAYED.format({when: played});
|
||
|
}
|
||
|
|
||
|
return (
|
||
|
<div className="last-played">
|
||
|
<HoverRoll hoverText={exePath ? exePath.toUpperCase() : ''}>
|
||
|
{lastPlayed}
|
||
|
</HoverRoll>
|
||
|
</div>
|
||
|
);
|
||
|
}
|
||
|
|
||
|
renderOverlayToggle() {
|
||
|
const {game} = this.props;
|
||
|
|
||
|
if (!enableOverlayToggle || game == null) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
const {overlay, overlayWarn} = game;
|
||
|
|
||
|
const text = overlay ? i18n.Messages.SETTINGS_GAMES_OVERLAY_ON : i18n.Messages.SETTINGS_GAMES_OVERLAY_OFF;
|
||
|
const icon = overlay
|
||
|
? <OverlayIconOn className="overlay-toggle-icon-on" />
|
||
|
: <OverlayIconOff className="overlay-toggle-icon-off" />;
|
||
|
const overlayWarning = overlayWarn
|
||
|
? <Tooltip text={i18n.Messages.SETTINGS_GAMES_OVERLAY_WARNING}>
|
||
|
<i className="overlay-warning-icon" />
|
||
|
</Tooltip>
|
||
|
: null;
|
||
|
|
||
|
return (
|
||
|
<div className="flex-center flex-nowrap flex-justify-between">
|
||
|
{overlayWarning}
|
||
|
<div className="overlay-status-text">{text}</div>
|
||
|
<Tooltip text={i18n.Messages.SETTINGS_GAMES_TOGGLE_OVERLAY}>
|
||
|
<div className="overlay-toggle-icon" onClick={this.handleOverlayToggle}>{icon}</div>
|
||
|
</Tooltip>
|
||
|
</div>
|
||
|
);
|
||
|
}
|
||
|
|
||
|
renderRemove() {
|
||
|
return (
|
||
|
<div className="round-remove-button remove-game" onClick={this.handleRemoveClick}>
|
||
|
{i18n.Messages.REMOVE_KEYBIND}
|
||
|
</div>
|
||
|
);
|
||
|
}
|
||
|
|
||
|
render() {
|
||
|
const {nowPlaying, game} = this.props;
|
||
|
|
||
|
const exists = game != null;
|
||
|
const classes = classNames('flex-center', {
|
||
|
game: !nowPlaying,
|
||
|
'ui-hover-card': !nowPlaying,
|
||
|
'now-playing': exists && nowPlaying,
|
||
|
'not-detected': !exists && nowPlaying,
|
||
|
});
|
||
|
|
||
|
return (
|
||
|
<div className={classes}>
|
||
|
<div className="game-name-last-played flex-vertical">
|
||
|
{this.renderName()}
|
||
|
{this.renderLastPlayed()}
|
||
|
</div>
|
||
|
{this.renderOverlayToggle()}
|
||
|
{!nowPlaying && exists ? this.renderRemove() : null}
|
||
|
</div>
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// Handlers
|
||
|
|
||
|
handleRemoveClick() {
|
||
|
GamesActionCreators.deleteEntry(this.props.game);
|
||
|
}
|
||
|
|
||
|
handleNameEdit() {
|
||
|
const {game} = this.props;
|
||
|
const {editingName} = this.state;
|
||
|
if (game.gameName !== editingName) {
|
||
|
GamesActionCreators.editName(game, editingName);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
handleNameChange(event) {
|
||
|
this.setState({editingName: event.target.value});
|
||
|
}
|
||
|
|
||
|
handleEnterKey(event) {
|
||
|
if (event.keyCode == 13) {
|
||
|
event.target.blur();
|
||
|
event.preventDefault();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
handleOverlayToggle() {
|
||
|
GamesActionCreators.toggleOverlay(this.props.game);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class UserSettingsGames extends React.PureComponent {
|
||
|
props: {
|
||
|
showCurrentGame: boolean,
|
||
|
gameHistory: Array<RunningGame>,
|
||
|
runningGame: RunningGame,
|
||
|
};
|
||
|
|
||
|
componentDidMount() {
|
||
|
RunningGameStore.watchCandidateGames(true);
|
||
|
}
|
||
|
|
||
|
componentWillUnmount() {
|
||
|
RunningGameStore.watchCandidateGames(false);
|
||
|
}
|
||
|
|
||
|
handleChangeCurrentGame(e) {
|
||
|
UserSettingsActionCreators.updateRemoteSettings({showCurrentGame: e.currentTarget.checked});
|
||
|
}
|
||
|
|
||
|
renderPopout(props) {
|
||
|
return <AddGamePopoutConnected {...props} />;
|
||
|
}
|
||
|
|
||
|
renderGameList() {
|
||
|
const {gameHistory} = this.props;
|
||
|
|
||
|
// Empty state message
|
||
|
if (!gameHistory.length) {
|
||
|
return (
|
||
|
<ThemedEmptyState className="margin-top-40">
|
||
|
<EmptyStateImage
|
||
|
darkSrc={require('../../images/empties/empty_user_settings_games_dark.svg')}
|
||
|
lightSrc={require('../../images/empties/empty_user_settings_games_light.svg')}
|
||
|
width={430}
|
||
|
height={250}
|
||
|
/>
|
||
|
<EmptyStateText>
|
||
|
{i18n.Messages.SETTINGS_GAMES_NO_GAMES_HEADER}
|
||
|
</EmptyStateText>
|
||
|
</ThemedEmptyState>
|
||
|
);
|
||
|
} else {
|
||
|
return (
|
||
|
<FormSection className="games-list">
|
||
|
<FormTitle className="margin-bottom-4">{i18n.Messages.SETTINGS_GAMES_ADDED_GAMES_LABEL}</FormTitle>
|
||
|
{gameHistory.map(game => <Game key={game.gameName} game={game} />)}
|
||
|
</FormSection>
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
render() {
|
||
|
const {runningGame, showCurrentGame} = this.props;
|
||
|
|
||
|
return (
|
||
|
<FormSection className="user-settings-games" tag={FormTitleTags.H2} title={i18n.Messages.GAME_ACTIVITY}>
|
||
|
<Game game={runningGame} nowPlaying />
|
||
|
<div className="now-playing-add margin-reset margin-top-8 margin-bottom-20">
|
||
|
<span>{i18n.Messages.SETTINGS_GAMES_NOT_SEEING_GAME}</span>
|
||
|
<Popout closeOnScroll={false} render={this.renderPopout} shadow={false} position={Popout.BOTTOM}>
|
||
|
<Button look={ButtonLooks.LINK} size={ButtonSizes.MIN} color={ButtonColors.LINK}>
|
||
|
{i18n.Messages.SETTINGS_GAMES_ADD_GAME}
|
||
|
</Button>
|
||
|
</Popout>
|
||
|
</div>
|
||
|
<SwitchItem className="margin-bottom-40" value={showCurrentGame} onChange={this.handleChangeCurrentGame}>
|
||
|
{i18n.Messages.SHOW_CURRENT_GAME}
|
||
|
</SwitchItem>
|
||
|
{this.renderGameList()}
|
||
|
</FormSection>
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export default Flux.connectStores([UserSettingsStore, RunningGameStore], () => {
|
||
|
return {
|
||
|
showCurrentGame: UserSettingsStore.showCurrentGame,
|
||
|
gameHistory: RunningGameStore.getGamesSeen(),
|
||
|
runningGame: RunningGameStore.getRunningGame(),
|
||
|
};
|
||
|
})(UserSettingsGames);
|
||
|
|
||
|
|
||
|
|
||
|
// WEBPACK FOOTER //
|
||
|
// ./discord_app/components/user_settings/UserSettingsGames.js
|