Add listen button

This commit is contained in:
sienori 2020-05-01 23:35:48 +09:00
parent 32dd96d03c
commit c162de041d
13 changed files with 162 additions and 81 deletions

View file

@ -33,6 +33,9 @@
"copiedLabel": { "copiedLabel": {
"message": "Copied." "message": "Copied."
}, },
"listenLabel": {
"message": "Listen"
},
"targetLangLabel": { "targetLangLabel": {
"message": "Target language" "message": "Target language"
}, },

View file

@ -0,0 +1,38 @@
import React, { Component } from "react";
import browser from "webextension-polyfill";
import { CopyToClipboard } from "react-copy-to-clipboard";
import CopyIcon from "../icons/copy.svg";
import "../styles/CopyButton.scss";
export default class CopyButton extends Component {
constructor(props) {
super(props);
this.state = { isCopied: false };
}
handleCopy = () => {
this.setState({ isCopied: true });
};
componentWillReceiveProps(nextProps) {
if (this.props.text !== nextProps.text) this.setState({ isCopied: false });
}
render() {
const { text } = this.props;
if (!text) return null;
return (
<div className="copy">
{this.state.isCopied && (
<span className="copiedText">{browser.i18n.getMessage("copiedLabel")}</span>
)}
<CopyToClipboard text={text} onCopy={this.handleCopy}>
<button className="copyButton" title={browser.i18n.getMessage("copyLabel")}>
<CopyIcon />
</button>
</CopyToClipboard>
</div>
);
}
}

View file

@ -1,6 +1,7 @@
import React, { Component } from "react"; import React, { Component } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import browser from "webextension-polyfill"; import browser from "webextension-polyfill";
import ListenButton from "./ListenButton";
import "../styles/inputArea.scss"; import "../styles/inputArea.scss";
export default class InputArea extends Component { export default class InputArea extends Component {
@ -16,7 +17,9 @@ export default class InputArea extends Component {
}; };
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
const shouldUpdate = this.props.inputText !== nextProps.inputText; const shouldUpdate =
this.props.inputText !== nextProps.inputText ||
this.props.sourceLang !== nextProps.sourceLang;
return shouldUpdate; return shouldUpdate;
} }
@ -25,16 +28,20 @@ export default class InputArea extends Component {
}; };
render() { render() {
const { inputText, sourceLang } = this.props;
return ( return (
<div id="inputArea"> <div id="inputArea">
<textarea <textarea
value={this.props.inputText} value={inputText}
ref="textarea" ref="textarea"
placeholder={browser.i18n.getMessage("initialTextArea")} placeholder={browser.i18n.getMessage("initialTextArea")}
onChange={this.handleInputText} onChange={this.handleInputText}
autoFocus autoFocus
spellCheck={false} spellCheck={false}
/> />
<div className="listen">
{sourceLang && <ListenButton text={inputText} lang={sourceLang} />}
</div>
</div> </div>
); );
} }

View file

@ -0,0 +1,32 @@
import React from "react";
import browser from "webextension-polyfill";
import log from "loglevel";
import SpeakerIcon from "../icons/speaker.svg";
import "../styles/ListenButton.scss";
const logDir = "popup/AudioButton";
const playAudio = async (text, lang) => {
const url = `https://translate.google.com/translate_tts?client=tw-ob&q=${encodeURIComponent(
text
)}&tl=${lang}`;
const audio = new Audio(url);
audio.load();
await audio.play().catch(e => log.error(logDir, "playAudio()", e, url));
};
export default props => {
const { text, lang } = props;
const canListen = text && text.length < 200;
if (!canListen) return null;
return (
<button
className="listenButton"
onClick={() => playAudio(text, lang)}
title={browser.i18n.getMessage("listenLabel")}
>
<SpeakerIcon />
</button>
);
};

View file

@ -1,40 +0,0 @@
import React, { Component } from "react";
import browser from "webextension-polyfill";
import { CopyToClipboard } from "react-copy-to-clipboard";
import CopyIcon from "../icons/copy.svg";
import "../styles/MediaButtons.scss";
export default class CopyButton extends Component {
constructor(props) {
super(props);
this.state = { isCopied: false };
}
handleCopy = () => {
this.setState({ isCopied: true });
};
componentWillReceiveProps(nextProps) {
if (this.props.resultText !== nextProps.resultText) this.setState({ isCopied: false });
}
render() {
const { resultText } = this.props;
return (
resultText && (
<div className="mediaButtons">
<div className="copy">
{this.state.isCopied && (
<span className="copiedText">{browser.i18n.getMessage("copiedLabel")}</span>
)}
<CopyToClipboard text={resultText} onCopy={this.handleCopy}>
<button className="copyButton" title={browser.i18n.getMessage("copyLabel")}>
<CopyIcon />
</button>
</CopyToClipboard>
</div>
</div>
)
);
}
}

View file

@ -39,6 +39,7 @@ export default class PopupPage extends Component {
inputText: "", inputText: "",
resultText: "", resultText: "",
candidateText: "", candidateText: "",
sourceLang: "",
statusText: "OK", statusText: "OK",
tabUrl: "", tabUrl: "",
isConnected: true, isConnected: true,
@ -93,7 +94,8 @@ export default class PopupPage extends Component {
this.setState({ this.setState({
resultText: result.resultText, resultText: result.resultText,
candidateText: result.candidateText, candidateText: result.candidateText,
statusText: result.statusText statusText: result.statusText,
sourceLang: result.sourceLanguage
}); });
return result; return result;
}; };
@ -143,7 +145,11 @@ export default class PopupPage extends Component {
isEnabledOnPage={this.state.isEnabledOnPage} isEnabledOnPage={this.state.isEnabledOnPage}
isConnected={this.state.isConnected} isConnected={this.state.isConnected}
/> />
<InputArea inputText={this.state.inputText} handleInputText={this.handleInputText} /> <InputArea
inputText={this.state.inputText}
handleInputText={this.handleInputText}
sourceLang={this.state.sourceLang}
/>
<hr /> <hr />
<ResultArea <ResultArea
inputText={this.state.inputText} inputText={this.state.inputText}

View file

@ -3,8 +3,9 @@ import browser from "webextension-polyfill";
import getErrorMessage from "src/common/getErrorMessage"; import getErrorMessage from "src/common/getErrorMessage";
import { getSettings } from "src/settings/settings"; import { getSettings } from "src/settings/settings";
import openUrl from "src/common/openUrl"; import openUrl from "src/common/openUrl";
import CopyButton from "./CopyButton";
import ListenButton from "./ListenButton";
import "../styles/ResultArea.scss"; import "../styles/ResultArea.scss";
import MediaButtons from "./MediaButtons";
const splitLine = text => { const splitLine = text => {
const regex = /(\n)/g; const regex = /(\n)/g;
@ -12,7 +13,7 @@ const splitLine = text => {
}; };
export default props => { export default props => {
const { resultText, candidateText, statusText } = props; const { resultText, candidateText, statusText, targetLang } = props;
const isError = statusText !== "OK"; const isError = statusText !== "OK";
const shouldShowCandidate = getSettings("ifShowCandidate"); const shouldShowCandidate = getSettings("ifShowCandidate");
@ -33,7 +34,10 @@ export default props => {
<a onClick={handleLinkClick}>{browser.i18n.getMessage("openInGoogleLabel")}</a> <a onClick={handleLinkClick}>{browser.i18n.getMessage("openInGoogleLabel")}</a>
</p> </p>
)} )}
<MediaButtons resultText={resultText} /> <div className="mediaButtons">
<CopyButton text={resultText} />
<ListenButton text={resultText} lang={targetLang} />
</div>
</div> </div>
); );
}; };

View file

@ -0,0 +1,3 @@
<svg viewBox="2 2 20 20">
<path d="M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.84 14,18.7V20.77C18,19.86 21,16.28 21,12C21,7.72 18,4.14 14,3.23M16.5,12C16.5,10.23 15.5,8.71 14,7.97V16C15.5,15.29 16.5,13.76 16.5,12M3,9V15H7L12,20V4L7,9H3Z" />
</svg>

After

Width:  |  Height:  |  Size: 265 B

View file

@ -0,0 +1,27 @@
.copy {
display: flex;
justify-content: center;
.copiedText {
color: var(--sub-text);
font-size: 12px;
margin-right: 5px;
-moz-user-select: none;
-webkit-user-select: none;
}
.copyButton {
padding: 0;
border: 0;
background-color: var(--main-bg);
cursor: pointer;
svg {
height: 16px;
width: 16px;
fill: var(--sub-text);
&:hover {
fill: var(--highlight);
}
}
}
}

View file

@ -1,5 +1,7 @@
#inputArea { #inputArea {
position: relative;
margin: 10px; margin: 10px;
textarea { textarea {
font: inherit; font: inherit;
resize: none; resize: none;
@ -23,4 +25,11 @@
textarea:focus { textarea:focus {
border-color: var(--highlight); border-color: var(--highlight);
} }
.listen {
position: absolute;
height: 16px;
right: 5px;
bottom: 5px;
}
} }

View file

@ -0,0 +1,15 @@
.listenButton {
padding: 0;
border: 0;
background-color: rgba(0, 0, 0, 0);
cursor: pointer;
svg {
height: 16px;
width: 16px;
fill: var(--sub-text);
&:hover {
fill: var(--highlight);
}
}
}

View file

@ -1,33 +0,0 @@
.mediaButtons {
display: flex;
flex-direction: row;
justify-content: flex-end;
.copy {
display: flex;
justify-content: center;
.copiedText {
color: var(--sub-text);
font-size: 12px;
margin-right: 5px;
-moz-user-select: none;
-webkit-user-select: none;
}
.copyButton {
padding: 0;
border: 0;
background-color: var(--main-bg);
cursor: pointer;
svg {
height: 16px;
width: 16px;
fill: var(--sub-text);
&:hover {
fill: var(--highlight);
}
}
}
}
}

View file

@ -4,7 +4,7 @@
overflow-y: auto; overflow-y: auto;
word-wrap: break-word; word-wrap: break-word;
background-color: var(--main-bg); background-color: var(--main-bg);
margin: 10px; margin: 10px 10px 3px;
p { p {
margin: 0; margin: 0;
@ -35,4 +35,14 @@
} }
} }
} }
.mediaButtons {
display: flex;
flex-direction: row;
justify-content: flex-end;
margin-right: 5px;
& > * {
margin-left: 10px;
}
}
} }