Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/oceans/components/common/Button.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import Radium from "radium";
import PropTypes from "prop-types";

import guide from "@ml/oceans/models/guide";
import soundLibrary from "@ml/oceans/models/soundLibrary";
import styles from "@ml/oceans/styles";

const UnwrappedButton = class Button extends React.Component {
static propTypes = {
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
onClick: PropTypes.func,
sound: PropTypes.string
};

onClick = event => {
guide.dismissCurrentGuide();
const clickReturnValue = this.props.onClick(event);

if (clickReturnValue !== false) {
const sound = this.props.sound || 'other';
soundLibrary.playSound(sound);
}
};

render() {
return (
<button
type="button"
className={this.props.className}
style={[styles.button, this.props.style]}
onClick={this.onClick}
>
{this.props.children}
</button>
);
}
};

export default Radium(UnwrappedButton);
59 changes: 59 additions & 0 deletions src/oceans/components/common/ConfirmationDialog.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React from 'react';
import PropTypes from 'prop-types';
import Radium from "radium";

import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faEraser} from "@fortawesome/free-solid-svg-icons";

import snail from "@public/images/snail-large.png";

import styles from "@ml/oceans/styles";
import I18n from "@ml/oceans/i18n";
import Button from "@ml/oceans/components/common/Button";

let UnwrappedConfirmationDialog = class ConfirmationDialog extends React.Component {
static propTypes = {
onYesClick: PropTypes.func.isRequired,
onNoClick: PropTypes.func.isRequired
};

render() {
return (
<div style={styles.confirmationDialogBackground}>
<div style={styles.confirmationDialog}>
<div style={styles.confirmationDialogContent}>
<img src={snail} style={styles.confirmationDialogImg}/>
<div>
<div
style={styles.confirmationHeader}
className="confirmation-text"
>
{I18n.t('areYouSure')}
</div>
<div style={styles.confirmationText}>
{I18n.t('eraseWarning')}
</div>
</div>
</div>
<div style={styles.confirmationButtons}>
<Button
onClick={this.props.onYesClick}
style={styles.confirmationYesButton}
className="dialog-button"
>
<FontAwesomeIcon icon={faEraser}/> {I18n.t('erase')}
</Button>
<Button
onClick={this.props.onNoClick}
style={styles.confirmationNoButton}
className="dialog-button"
>
{I18n.t('cancel')}
</Button>
</div>
</div>
</div>
);
}
};
export default Radium(UnwrappedConfirmationDialog);
123 changes: 123 additions & 0 deletions src/oceans/components/common/Guide.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import React from 'react'
import Radium from "radium";
import Typist from "react-typist";

import {getState, setState} from "@ml/oceans/state";
import guide from "@ml/oceans/models/guide";
import soundLibrary from "@ml/oceans/models/soundLibrary";
import styles from "@ml/oceans/styles";
import colors from "@ml/oceans/styles/colors";
import I18n from "@ml/oceans/i18n";
import {Button} from "@ml/oceans/components/common";
import arrowDownImage from "@public/images/arrow-down.png";

let UnwrappedGuide = class Guide extends React.Component {
onShowing() {
clearInterval(getState().guideTypingTimer);
setState({guideShowing: true, guideTypingTimer: null});
}

dismissGuideClick() {
const dismissed = guide.dismissCurrentGuide();
if (dismissed) {
soundLibrary.playSound('other');
}
}

render() {
const state = getState();
const currentGuide = guide.getCurrentGuide();

let guideBgStyle = [styles.guideBackground];
if (currentGuide) {
if (currentGuide.noDimBackground) {
guideBgStyle = [styles.guideBackgroundHidden];
}

// Info guides should have a darker background color.
if (currentGuide.style === 'Info') {
guideBgStyle.push({backgroundColor: colors.transparentBlack});
}
}

// Start playing the typing sounds.
if (!state.guideShowing && !state.guideTypingTimer && currentGuide) {
const guideTypingTimer = setInterval(() => {
soundLibrary.playSound('no', 0.5);
}, 1000 / 10);
setState({guideTypingTimer});
}

return (
<div>
{currentGuide && currentGuide.image && (
<img
src={currentGuide.image}
style={[styles.guideImage, currentGuide.imageStyle || {}]}
/>
)}
{!!currentGuide && (
<div>
<div
key={currentGuide.id}
style={guideBgStyle}
onClick={this.dismissGuideClick}
id="uitest-dismiss-guide"
>
<div
style={{
...styles.guide,
...styles[`guide${currentGuide.style}`]
}}
>
<div>
{currentGuide.style === 'Info' && (
<div style={styles.guideHeading}>
{I18n.t('didYouKnow')}
</div>
)}
<div style={styles.guideTypingText}>
<Typist
avgTypingDelay={35}
stdTypingDelay={15}
cursor={{show: false}}
onTypingDone={this.onShowing}
>
{currentGuide.textFn(getState())}
</Typist>
</div>
<div
style={
currentGuide.style === 'Info'
? styles.guideFinalTextInfoContainer
: styles.guideFinalTextContainer
}
>
<div style={styles.guideFinalText}>
{currentGuide.textFn(getState())}
</div>
</div>
{currentGuide.style === 'Info' && (
<Button style={styles.infoGuideButton} onClick={() => {}}>
{I18n.t('continue')}
</Button>
)}
</div>
</div>
</div>
{currentGuide.arrow && (
<img
src={arrowDownImage}
style={{
...styles.guideArrow,
...styles[`arrow${currentGuide.arrow}`]
}}
/>
)}
</div>
)}
</div>
);
}
};
export default Radium(UnwrappedGuide);
34 changes: 34 additions & 0 deletions src/oceans/components/common/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react'
import PropTypes from 'prop-types'

import styles from "@ml/oceans/styles";

import Guide from "@ml/oceans/components/common/Guide";
import Button from "@ml/oceans/components/common/Button";
import ConfirmationDialog from "@ml/oceans/components/common/ConfirmationDialog";
import loadingGif from "@public/images/loading.gif";

const Body = ({onClick, children}) => (
<div style={styles.body} onClick={onClick}>
{children}
<Guide />
</div>
)
Body.propTypes = {
children: PropTypes.node,
onClick: PropTypes.func
}

const Content = ({children}) => (<div style={styles.content}>{children}</div>)
Content.propTypes = {
children: PropTypes.node
};

const Loading = () => (
<Body>
<img src={loadingGif} style={styles.loading} />
</Body>
)


export {Body, Content, Loading, Guide, Button, ConfirmationDialog}
134 changes: 134 additions & 0 deletions src/oceans/components/scenes/pond/PondPanel.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import React from "react";
import {getState, setState} from "@ml/oceans/state";
import styles from "@ml/oceans/styles";
import I18n from "@ml/oceans/i18n";
import Markdown from "@ml/utils/Markdown";

class PondPanel extends React.Component {
onPondPanelClick = e => {
setState({pondPanelShowing: false});
e.stopPropagation();
};

render() {
const state = getState();

const maxExplainValue = state.showRecallFish
? state.pondRecallFishMaxExplainValue
: state.pondFishMaxExplainValue;

return (
<div>
{!state.pondClickedFish && (
<div style={styles.pondPanelLeft} onClick={this.onPondPanelClick}>
{state.pondExplainGeneralSummary && (
<div>
<div style={styles.pondPanelPreText}>
{I18n.t('mostImportantParts')}
</div>
{state.pondExplainGeneralSummary.slice(0, 5).map((f, i) => (
<div key={i}>
{f.importance > 0 && (
<div style={styles.pondPanelRow}>
&nbsp;
<div
style={{
...styles.pondPanelGeneralBar,
width:
(Math.abs(f.importance) /
state.pondExplainGeneralSummary[0].importance) *
100 +
'%'
}}
>
&nbsp;
</div>
<div style={styles.pondPanelGeneralBarText}>
{I18n.t(f.partType)}
</div>
</div>
)}
</div>
))}
<div style={styles.pondPanelPostText}>
{I18n.t('clickIndividualFish')}
</div>
</div>
)}
</div>
)}
{state.pondClickedFish && (
<div
style={
state.pondPanelSide === 'left'
? styles.pondPanelLeft
: styles.pondPanelRight
}
onClick={e => this.onPondPanelClick(e)}
>
{state.pondExplainFishSummary && (
<div>
<div style={styles.pondPanelPreText} id="pondTextMarkdown">
<Markdown
markdown={I18n.t(
'mostImportantPartsDescription',
{
word: state.word.toLowerCase(),
notWord: I18n.t('notWord', {
word: state.word
}).toLowerCase()
}
)}
/>
</div>
{state.pondExplainFishSummary.slice(0, 4).map((f, i) => (
<div key={i}>
{f.impact < 0 && (
<div style={styles.pondPanelRow}>
&nbsp;
<div
style={{
...styles.pondPanelGreenBar,
width:
((Math.abs(f.impact) / maxExplainValue) * 100) /
2 +
'%'
}}
>
&nbsp;
</div>
<div style={styles.pondPanelGreenBarText}>
{I18n.t(f.partType)}
</div>
</div>
)}
{f.impact > 0 && (
<div style={styles.pondPanelRow}>
&nbsp;
<div
style={{
...styles.pondPanelRedBar,
width:
((Math.abs(f.impact) / maxExplainValue) * 100) /
2 +
'%'
}}
>
&nbsp;
</div>
<div style={styles.pondPanelRedBarText}>
{I18n.t(f.partType)}
</div>
</div>
)}
</div>
))}
</div>
)}
</div>
)}
</div>
);
}
}
export default PondPanel;
Loading