Skip to content

Commit

Permalink
Move some state from shared voices to options voices
Browse files Browse the repository at this point in the history
  • Loading branch information
joelpurra committed Dec 9, 2017
1 parent 133a4d3 commit 4603856
Show file tree
Hide file tree
Showing 16 changed files with 276 additions and 193 deletions.
2 changes: 2 additions & 0 deletions src/options/actions/index.js
Expand Up @@ -21,9 +21,11 @@ along with Talkie. If not, see <https://www.gnu.org/licenses/>.
import shared from "../../shared/actions";
import unshared from "../../unshared/actions";
import * as navigation from "./navigation";
import * as voices from "./voices";

export default {
shared,
unshared,
navigation,
voices,
};
163 changes: 163 additions & 0 deletions src/options/actions/voices.js
@@ -0,0 +1,163 @@
/*
This file is part of Talkie -- text-to-speech browser extension button.
<https://joelpurra.com/projects/talkie/>
Copyright (c) 2016, 2017 Joel Purra <https://joelpurra.com/>
Talkie is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Talkie is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Talkie. If not, see <https://www.gnu.org/licenses/>.
*/

import * as actionTypes from "../constants/action-types-voices";

/*eslint no-unused-vars: ["warn", { "args": "after-used" }] */

export const loadSelectedLanguageCode = (languageCode) =>
(dispatch) => {
dispatch(setSelectedLanguageCode(languageCode));

return dispatch(loadEffectiveVoiceForLanguage(languageCode));
};

const setSelectedLanguageCode = (selectedLanguageCode) => {
return { type: actionTypes.SET_SELECTED_LANGUAGE_CODE, selectedLanguageCode };
};

export const loadSelectedVoiceName = (voiceName) =>
(dispatch, getState) => {
const state = getState();

// TODO: move to function and/or state?
let existingVoiceName = null;

if (
state
&& state.shared
&& state.shared.voices
&& state.shared.voices.voices
&& state.shared.voices.voices.some((voice) => voice.name === voiceName)
) {
existingVoiceName = voiceName;
}

return Promise.all([
dispatch(setSelectedVoiceName(existingVoiceName)),
dispatch(loadEffectiveRateForVoice(existingVoiceName)),
dispatch(loadEffectivePitchForVoice(existingVoiceName)),
])
.then(() => dispatch(speakState()));
};

const setSelectedVoiceName = (selectedVoiceName) => {
return { type: actionTypes.SET_SELECTED_VOICE_NAME, selectedVoiceName };
};

export const loadSampleText = () =>
(dispatch, getState, api) => api.getSampleText()
.then((sampleText) => dispatch(setSampleText(sampleText)));

export const setSampleText = (sampleText) => {
return { type: actionTypes.SET_SAMPLE_TEXT, sampleText };
};

export const loadEffectiveRateForVoice = (voiceName) =>
(dispatch, getState, api) => api.getEffectiveRateForVoice(voiceName)
.then((effectiveRateForVoice) => dispatch(setRateForSelectedVoice(effectiveRateForVoice)));

export const storeVoiceRateOverride = (voiceName, rate) =>
(dispatch, getState, api) => api.setVoiceRateOverride(voiceName, rate)
.then(() => dispatch(setRateForSelectedVoice(rate)));

export const setRateForSelectedVoice = (rateForSelectedVoice) =>
(dispatch) => Promise.resolve()
.then(() => dispatch({ type: actionTypes.SET_RATE_FOR_SELECTED_VOICE, rateForSelectedVoice }))
.then(() => dispatch(speakState()));

export const loadEffectivePitchForVoice = (voiceName) =>
(dispatch, getState, api) => api.getEffectivePitchForVoice(voiceName)
.then((effectivePitchForVoice) => dispatch(setPitchForSelectedVoice(effectivePitchForVoice)));

export const storeVoicePitchOverride = (voiceName, pitch) =>
(dispatch, getState, api) => api.setVoicePitchOverride(voiceName, pitch)
.then(() => dispatch(setPitchForSelectedVoice(pitch)));

export const setPitchForSelectedVoice = (pitchForSelectedVoice) =>
(dispatch) => Promise.resolve()
.then(() => dispatch({ type: actionTypes.SET_PITCH_FOR_SELECTED_VOICE, pitchForSelectedVoice }))
.then(() => dispatch(speakState()));

export const loadEffectiveVoiceForLanguage = (languageCode) =>
(dispatch, getState, api) => {
if (languageCode) {
return api.getEffectiveVoiceForLanguage(languageCode)
.then((effectiveVoiceForLanguage) => Promise.all([
dispatch(setEffectiveVoiceNameForSelectedLanguage(effectiveVoiceForLanguage)),
dispatch(loadSelectedVoiceName(effectiveVoiceForLanguage)),
]));
}

const state = getState();

const newSelectedVoiceName = (state.shared.voices.voices.length > 0 && state.shared.voices.voices[0].name) || null;

return Promise.all([
dispatch(setEffectiveVoiceNameForSelectedLanguage(null)),
dispatch(loadSelectedVoiceName(newSelectedVoiceName)),
]);
};

export const storeEffectiveVoiceNameForLanguage = (languageCode, voiceName) =>
(dispatch, getState, api) => api.toggleLanguageVoiceOverrideName(languageCode, voiceName)
.then(() => dispatch(loadEffectiveVoiceForLanguage(languageCode)));

export const setEffectiveVoiceNameForSelectedLanguage = (effectiveVoiceNameForSelectedLanguage) => {
return { type: actionTypes.SET_DEFAULT_VOICE_NAME_FOR_SELECTED_LANGUAGE, effectiveVoiceNameForSelectedLanguage };
};

export const speakState = () =>
(dispatch, getState, api) => Promise.resolve()
.then(() => {
const state = getState();

// TODO: move to function and/or state?
const readyToSpeak = state
&& state.voices
&& typeof state.voices.sampleText === "string"
&& state.voices.sampleText.length > 0
&& (
(
typeof state.voices.selectedLanguageCode === "string"
&& state.voices.selectedLanguageCode.length > 0
)
|| (
typeof state.voices.selectedVoiceName === "string"
&& state.voices.selectedVoiceName.length > 0
)
)
&& !isNaN(state.voices.rateForSelectedVoice)
&& !isNaN(state.voices.pitchForSelectedVoice);

if (readyToSpeak) {
const text = state.voices.sampleText;
const voice = {
lang: state.voices.selectedLanguageCode || null,
name: state.voices.selectedVoiceName || null,
rate: state.voices.rateForSelectedVoice,
pitch: state.voices.pitchForSelectedVoice,
};

return api.debouncedSpeak(text, voice);
}

return undefined;
});
2 changes: 1 addition & 1 deletion src/options/components/sections/text.jsx
Expand Up @@ -41,7 +41,7 @@ export default class Text extends React.PureComponent {
}

handleSpeakLongTextsChange(speakLongTexts) {
this.props.actions.storeSpeakLongTexts(speakLongTexts);
this.props.actions.sharedVoices.storeSpeakLongTexts(speakLongTexts);
}

render() {
Expand Down
12 changes: 6 additions & 6 deletions src/options/components/sections/voices.jsx
Expand Up @@ -92,27 +92,27 @@ export default class Voices extends React.PureComponent {
}

handleLanguageChange(value) {
this.props.actions.loadSelectedLanguageCode(value);
this.props.actions.voices.loadSelectedLanguageCode(value);
}

handleVoiceChange(value) {
this.props.actions.loadSelectedVoiceName(value);
this.props.actions.voices.loadSelectedVoiceName(value);
}

handleSampleTextChange(value) {
this.props.actions.setSampleText(value);
this.props.actions.voices.setSampleText(value);
}

handleToogleDefaultClick() {
this.props.actions.storeEffectiveVoiceNameForLanguage(this.props.selectedLanguageCode, this.props.selectedVoiceName);
this.props.actions.voices.storeEffectiveVoiceNameForLanguage(this.props.selectedLanguageCode, this.props.selectedVoiceName);
}

handleRateChange(value) {
this.props.actions.storeVoiceRateOverride(this.props.selectedVoiceName, value);
this.props.actions.voices.storeVoiceRateOverride(this.props.selectedVoiceName, value);
}

handlePitchChange(value) {
this.props.actions.storeVoicePitchOverride(this.props.selectedVoiceName, value);
this.props.actions.voices.storeVoicePitchOverride(this.props.selectedVoiceName, value);
}

render() {
Expand Down
31 changes: 31 additions & 0 deletions src/options/constants/action-types-voices.js
@@ -0,0 +1,31 @@
/*
This file is part of Talkie -- text-to-speech browser extension button.
<https://joelpurra.com/projects/talkie/>
Copyright (c) 2016, 2017 Joel Purra <https://joelpurra.com/>
Talkie is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Talkie is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Talkie. If not, see <https://www.gnu.org/licenses/>.
*/

export const SET_SELECTED_LANGUAGE_CODE = "SET_SELECTED_LANGUAGE_CODE";

export const SET_SELECTED_VOICE_NAME = "SET_SELECTED_VOICE_NAME";

export const SET_DEFAULT_VOICE_NAME_FOR_SELECTED_LANGUAGE = "SET_DEFAULT_VOICE_NAME_FOR_SELECTED_LANGUAGE";

export const SET_SAMPLE_TEXT = "SET_SAMPLE_TEXT";

export const SET_RATE_FOR_SELECTED_VOICE = "SET_RATE_FOR_SELECTED_VOICE";

export const SET_PITCH_FOR_SELECTED_VOICE = "SET_PITCH_FOR_SELECTED_VOICE";
2 changes: 0 additions & 2 deletions src/options/containers/about-container.jsx
Expand Up @@ -75,8 +75,6 @@ export default class AboutContainer extends React.PureComponent {
this.props.actions.sharedVoices.loadVoices();
this.props.actions.sharedVoices.loadNavigatorLanguage();
this.props.actions.sharedVoices.loadNavigatorLanguages();
this.props.actions.sharedVoices.loadTranslatedLanguages();
this.props.actions.sharedMetadata.loadVersionName();
}

static defaultProps = {
Expand Down
6 changes: 4 additions & 2 deletions src/options/containers/text-container.jsx
Expand Up @@ -41,14 +41,16 @@ const mapStateToProps = (state) => {

const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(actionCreators.shared.voices, dispatch),
actions: {
sharedVoices: bindActionCreators(actionCreators.shared.voices, dispatch),
},
};
};

@connect(mapStateToProps, mapDispatchToProps)
export default class VoicesContainer extends React.PureComponent {
componentDidMount() {
this.props.actions.loadSpeakLongTexts();
this.props.actions.sharedVoices.loadSpeakLongTexts();
}

static propTypes = {
Expand Down
21 changes: 12 additions & 9 deletions src/options/containers/voices-container.jsx
Expand Up @@ -41,28 +41,31 @@ const mapStateToProps = (state) => {
return {
voices: state.shared.voices.voices,
languages: getLanguagesFromVoices(state.shared.voices.voices),
selectedLanguageCode: state.shared.voices.selectedLanguageCode,
selectedVoiceName: state.shared.voices.selectedVoiceName,
effectiveVoiceNameForSelectedLanguage: state.shared.voices.effectiveVoiceNameForSelectedLanguage,
sampleText: state.shared.voices.sampleText,
rateForSelectedVoice: state.shared.voices.rateForSelectedVoice,
pitchForSelectedVoice: state.shared.voices.pitchForSelectedVoice,
selectedLanguageCode: state.voices.selectedLanguageCode,
selectedVoiceName: state.voices.selectedVoiceName,
effectiveVoiceNameForSelectedLanguage: state.voices.effectiveVoiceNameForSelectedLanguage,
sampleText: state.voices.sampleText,
rateForSelectedVoice: state.voices.rateForSelectedVoice,
pitchForSelectedVoice: state.voices.pitchForSelectedVoice,
isPremiumVersion: state.shared.metadata.isPremiumVersion,
};
};

const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(actionCreators.shared.voices, dispatch),
actions: {
voices: bindActionCreators(actionCreators.voices, dispatch),
sharedVoices: bindActionCreators(actionCreators.shared.voices, dispatch),
},
};
};

@connect(mapStateToProps, mapDispatchToProps)
export default class VoicesContainer extends React.PureComponent {
componentDidMount() {
// TODO: is this the best place to load data?
this.props.actions.loadVoices();
this.props.actions.loadSampleText();
this.props.actions.sharedVoices.loadVoices();
this.props.actions.voices.loadSampleText();
}

static propTypes = {
Expand Down
2 changes: 2 additions & 0 deletions src/options/reducers/index.js
Expand Up @@ -26,9 +26,11 @@ import shared from "../../shared/reducers";
import unshared from "../../unshared/reducers";

import navigation from "./navigation";
import voices from "./voices";

export default combineReducers({
shared,
unshared,
navigation,
voices,
});
47 changes: 47 additions & 0 deletions src/options/reducers/voices.js
@@ -0,0 +1,47 @@
/*
This file is part of Talkie -- text-to-speech browser extension button.
<https://joelpurra.com/projects/talkie/>
Copyright (c) 2016, 2017 Joel Purra <https://joelpurra.com/>
Talkie is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Talkie is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Talkie. If not, see <https://www.gnu.org/licenses/>.
*/

import {
createAssignmentActionMapReducer,
} from "../../shared/utils/reduce-helpers";

import * as actionTypes from "../constants/action-types-voices";

const initialState = {
selectedLanguageCode: null,
selectedVoiceName: null,
effectiveVoiceNameForSelectedLanguage: null,
sampleText: "",
rateForSelectedVoice: 1,
pitchForSelectedVoice: 1,
};

const customActionsMap = {};

const assignActionsMap = {
[actionTypes.SET_SELECTED_LANGUAGE_CODE]: "selectedLanguageCode",
[actionTypes.SET_SELECTED_VOICE_NAME]: "selectedVoiceName",
[actionTypes.SET_DEFAULT_VOICE_NAME_FOR_SELECTED_LANGUAGE]: "effectiveVoiceNameForSelectedLanguage",
[actionTypes.SET_SAMPLE_TEXT]: "sampleText",
[actionTypes.SET_RATE_FOR_SELECTED_VOICE]: "rateForSelectedVoice",
[actionTypes.SET_PITCH_FOR_SELECTED_VOICE]: "pitchForSelectedVoice",
};

export default createAssignmentActionMapReducer(initialState, customActionsMap, assignActionsMap);
6 changes: 2 additions & 4 deletions src/popup/popup.html.js
Expand Up @@ -21,14 +21,12 @@ along with Talkie. If not, see <https://www.gnu.org/licenses/>.
import getHtml from "../shared/renderers/render-react-html";

import rootReducer from "./reducers";
import actions from "./actions";
// import actions from "./actions";
import App from "./containers/app.jsx";
import htmlTemplate from "./popup.template.html";

// TODO: generalize preloading?
const prerenderActionsToDispatch = [
actions.shared.metadata.loadVersionNumber(),
];
const prerenderActionsToDispatch = [];
const postrenderActionsToDispatch = [];

export default (talkieLocale) => getHtml(rootReducer, prerenderActionsToDispatch, postrenderActionsToDispatch, htmlTemplate, talkieLocale, App);

0 comments on commit 4603856

Please sign in to comment.