Skip to content

Commit

Permalink
Merge branch 'unit/23-word-to-speech'
Browse files Browse the repository at this point in the history
  • Loading branch information
ElynnaChuang committed May 15, 2023
2 parents f42cd8c + 5aa59c7 commit 777290a
Show file tree
Hide file tree
Showing 13 changed files with 320 additions and 14 deletions.
1 change: 0 additions & 1 deletion .stylelintrc.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"extends": ["stylelint-config-standard-scss", "stylelint-config-clean-order"],
"rules": {
"indentation": 2,
"at-rule-no-unknown": null,
"scss/at-rule-no-unknown": true,
"selector-class-pattern": null,
Expand Down
13 changes: 12 additions & 1 deletion src/Components/03Input/index.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { useState } from 'react';
import styles from './styles.module.scss';

export const RangeInput = ({ label, name, initialValue, min, max, handleCgange }) => {
export const RangeInput = ({
label,
name,
initialValue,
min,
max,
step,
isDisabled = false,
handleCgange,
}) => {
const [value, setValue] = useState(initialValue);
const onChange = e => {
setValue(e.target.value);
Expand All @@ -18,7 +27,9 @@ export const RangeInput = ({ label, name, initialValue, min, max, handleCgange }
min={min}
max={max}
value={value}
step={step || 1}
onChange={e => onChange(e)}
disabled={isDisabled}
/>
</div>
);
Expand Down
24 changes: 19 additions & 5 deletions src/Components/03Input/styles.module.scss
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
.input_container {
display: flex;
flex-direction: column;
gap: 1rem;
gap: 0.75rem;
justify-content: flex-start;

width: 100%;

& input {
cursor: pointer;

Expand All @@ -15,17 +17,23 @@
}
}

$control_height: 1.2rem;
$control_height: 1.5rem;
$border_radius: 0.5rem;

input[type='range'] {
overflow: hidden;

height: $control_height;

background: none; // 清除不必要的東西
border: 1px solid #dedede;
border-radius: $border_radius;

// 進度條背景
&::-webkit-slider-runnable-track {
overflow: hidden;
height: $control_height;
background-color: #bcbcbc;
border-radius: 0.2rem;
}

// 調整拖曳鈕
Expand All @@ -35,14 +43,20 @@ input[type='range'] {

appearance: none;
background-color: #fff;
border-radius: 0.2rem;
border-radius: $border_radius;
box-shadow: -101vw 0 0 100vw #78acd0;
}

&:disabled {
cursor: not-allowed;
opacity: 0.6;
}
}

input[type='color'] {
height: $control_height;
border-radius: 0.2rem;
border: 1px solid #eaeaea;
border-radius: $border_radius;

&::-webkit-color-swatch-wrapper {
padding: 0;
Expand Down
18 changes: 18 additions & 0 deletions src/Components/Select/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import styles from './styles.module.scss';

export const Select = ({ name, selectClass, onChange, isDisable, options }) => {
return (
<select
name={name}
className={`${styles.select} ${selectClass}`}
onChange={e => onChange?.(e.target.value)}
disabled={isDisable}
>
{options.map(option => (
<option value={option.value} key={option.value}>
{option.name}
</option>
))}
</select>
);
};
13 changes: 13 additions & 0 deletions src/Components/Select/styles.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.select {
cursor: pointer;

padding: 0.5rem;

font-weight: bold;

appearance: none;
background-color: rgb(255 255 255);
border: none;
border-radius: 0.5rem;
box-shadow: 0 0 8px 0 rgba($color: #000, $alpha: 20%);
}
2 changes: 2 additions & 0 deletions src/Components/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ export { Title } from './Titles';

export { Card } from './Card';
export { Image } from './Image';

export { Select } from './Select';
11 changes: 9 additions & 2 deletions src/Pages/20SpeechRecognition/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { useSnackbar } from 'notistack';
import { useEffect, useRef, useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
import styles from './styles.module.scss';
import { Button, Select } from './component';
import { LayoutCol1 } from '@/Layouts';
import { Select } from '@/Components';
import { Button } from './component';

const languages = [
{ value: 'en-US', name: 'English(US)' },
Expand Down Expand Up @@ -94,7 +95,13 @@ const SpeechRecognitionPage = () => {
</p>
<p>
Language
<Select onChange={setLanguage} isDisable={recordStatus} options={languages} />
<Select
name='language'
selectClass={styles.select}
onChange={setLanguage}
isDisable={recordStatus}
options={languages}
/>
</p>
</div>
<div className={styles.controls_buttons}>
Expand Down
12 changes: 8 additions & 4 deletions src/Pages/20SpeechRecognition/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,18 @@ $btn_primary: #ffa845;
background-color: #eff0f6;
border-radius: 0.5rem;

& span {
span {
font-weight: 700;
color: $btn_primary;
}

select:disabled {
cursor: not-allowed;
opacity: 0.6;
select {
color: $btn_primary;

&:disabled {
cursor: not-allowed;
opacity: 0.6;
}
}
}
}
Expand Down
136 changes: 136 additions & 0 deletions src/Pages/23WordToSpeech/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { useEffect, useRef, useState } from 'react';
import { LayoutCol1 } from '@/Layouts';
import { Select, Title, RangeInput } from '@/Components';
import styles from './styles.module.scss';

const synth = window.speechSynthesis;
const getVoiceData = () => {
return new Promise(resolve => {
const timer = setInterval(() => {
if (synth.getVoices().length !== 0) {
resolve(synth.getVoices());
clearInterval(timer);
}
}, 10);
});
};

const WordToSpeechPage = () => {
const textRef = useRef(null);
const [options, setOptions] = useState([]);
const [voice, setVoice] = useState(null);
const [isSpeak, setIsSpeak] = useState(false);

const msg = new SpeechSynthesisUtterance();
msg.onend = () => setIsSpeak(false);

const speak = () => {
if (!textRef.current.value) return;

msg.text = textRef.current.value;
msg.voice = voice;

setIsSpeak(true);
synth.speak(msg);
};

const stopSpeak = () => {
synth.cancel();
setIsSpeak(false);
};

const handleChange = value => {
if (!options.length) return;

synth.cancel();
setIsSpeak(false);
setVoice(options.find(option => option.name === value));
};

useEffect(() => {
const setVoiceOptions = async () => {
const originData = await getVoiceData();
const finalData = originData
.filter(el => !el.name.toLowerCase().includes('google'))
.map(el => {
el.value = el.name;
return el;
});

setOptions(finalData);
};

setVoiceOptions();
}, []);

return (
<LayoutCol1 baseClassName={styles.page}>
<div className={styles.card}>
<Title title='Word to Speech' titleClassName={styles.card_header} size='s' />
<div className={styles.card_body}>
<Select
name='voice'
options={options}
selectClass={styles.select}
onChange={handleChange}
/>
<div className={styles.ranges}>
<RangeInput
min={0}
max={3}
step={0.1}
initialValue={1}
label='Rate'
name='rate'
isDisabled={isSpeak}
/>
<RangeInput
min={0}
max={2}
step={0.1}
initialValue={0}
label='Pitch'
name='pitch'
isDisabled={isSpeak}
/>
</div>
<div className={styles.buttons}>
<button disabled={!options.length || !isSpeak} onClick={stopSpeak}>
Stop
</button>
<button disabled={!options.length || isSpeak} onClick={speak}>
Speak
</button>
</div>
<Textarea
textRef={textRef}
name='text'
initialValue='Hello'
isDisabled={isSpeak}
/>
</div>
</div>
</LayoutCol1>
);
};

export default WordToSpeechPage;

const Textarea = ({ textRef, name, rows, initialValue, onChange, isDisabled }) => {
const [currentValue, setCurrentValue] = useState(initialValue);
const handleOnChange = e => {
setCurrentValue(e.target.value);
onChange?.(e.target.value);
};
return (
<textarea
ref={textRef}
name={name}
rows={rows}
className={styles.textarea}
value={currentValue}
onChange={handleOnChange}
disabled={isDisabled}
/>
);
};
Loading

0 comments on commit 777290a

Please sign in to comment.