diff --git a/src/assets/styles/fonts/icomoon.eot b/src/assets/styles/fonts/icomoon.eot
index 6a5f547f..533e216b 100644
Binary files a/src/assets/styles/fonts/icomoon.eot and b/src/assets/styles/fonts/icomoon.eot differ
diff --git a/src/assets/styles/fonts/icomoon.svg b/src/assets/styles/fonts/icomoon.svg
index 5708d7db..917b1be0 100644
--- a/src/assets/styles/fonts/icomoon.svg
+++ b/src/assets/styles/fonts/icomoon.svg
@@ -106,4 +106,5 @@
+
\ No newline at end of file
diff --git a/src/assets/styles/fonts/icomoon.ttf b/src/assets/styles/fonts/icomoon.ttf
index 16878248..6954fb8c 100644
Binary files a/src/assets/styles/fonts/icomoon.ttf and b/src/assets/styles/fonts/icomoon.ttf differ
diff --git a/src/assets/styles/fonts/icomoon.woff b/src/assets/styles/fonts/icomoon.woff
index ecae6108..4540153b 100644
Binary files a/src/assets/styles/fonts/icomoon.woff and b/src/assets/styles/fonts/icomoon.woff differ
diff --git a/src/assets/styles/style.css b/src/assets/styles/style.css
index 74a75489..4e85f129 100644
--- a/src/assets/styles/style.css
+++ b/src/assets/styles/style.css
@@ -1,10 +1,10 @@
@font-face {
font-family: 'icomoon';
- src: url('fonts/icomoon.eot?mz9umd');
- src: url('fonts/icomoon.eot?mz9umd#iefix') format('embedded-opentype'),
- url('fonts/icomoon.ttf?mz9umd') format('truetype'),
- url('fonts/icomoon.woff?mz9umd') format('woff'),
- url('fonts/icomoon.svg?mz9umd#icomoon') format('svg');
+ src: url('fonts/icomoon.eot?qn1hwh');
+ src: url('fonts/icomoon.eot?qn1hwh#iefix') format('embedded-opentype'),
+ url('fonts/icomoon.ttf?qn1hwh') format('truetype'),
+ url('fonts/icomoon.woff?qn1hwh') format('woff'),
+ url('fonts/icomoon.svg?qn1hwh#icomoon') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
@@ -25,6 +25,9 @@
-moz-osx-font-smoothing: grayscale;
}
+.icon-earphone:before {
+ content: "\e963";
+}
.icon-copy:before {
content: "\e962";
}
diff --git a/src/components/dialogs/speechDialog/component.tsx b/src/components/dialogs/speechDialog/component.tsx
new file mode 100644
index 00000000..5646b3ae
--- /dev/null
+++ b/src/components/dialogs/speechDialog/component.tsx
@@ -0,0 +1,45 @@
+import React from "react";
+import { SpeechDialogProps, SpeechDialogState } from "./interface";
+import "./speechDialog.css";
+import { ConfigService } from "../../../assets/lib/kookit-extra-browser.min";
+import TextToSpeech from "../../textToSpeech";
+
+class SpeechDialog extends React.Component<
+ SpeechDialogProps,
+ SpeechDialogState
+> {
+ constructor(props: SpeechDialogProps) {
+ super(props);
+ this.state = {
+ isShowExportAll: false,
+ isConvertPDF: ConfigService.getReaderConfig("isConvertPDF") === "yes",
+ };
+ }
+ componentDidMount(): void {
+ this.props.handleFetchPlugins();
+ }
+ render() {
+ return (
+ <>
+
{
+ this.props.handleSpeechDialog(true);
+ }}
+ style={{
+ right: "65px",
+ left: "auto",
+ top: "auto",
+ bottom: "60px",
+ width: "300px",
+ height: "400px",
+ }}
+ >
+ {this.props.plugins && }
+
+ >
+ );
+ }
+}
+
+export default SpeechDialog;
diff --git a/src/components/dialogs/speechDialog/index.tsx b/src/components/dialogs/speechDialog/index.tsx
new file mode 100644
index 00000000..82483c7b
--- /dev/null
+++ b/src/components/dialogs/speechDialog/index.tsx
@@ -0,0 +1,27 @@
+import { connect } from "react-redux";
+import {
+ handleSetting,
+ handleSpeechDialog,
+ handleFetchPlugins,
+} from "../../../store/actions";
+import { stateType } from "../../../store";
+import SpeechDialog from "./component";
+import { withTranslation } from "react-i18next";
+
+const mapStateToProps = (state: stateType) => {
+ return {
+ isSpeechOpen: state.reader.isSpeechOpen,
+ plugins: state.manager.plugins,
+ isAuthed: state.manager.isAuthed,
+ currentBook: state.book.currentBook,
+ };
+};
+const actionCreator = {
+ handleSetting,
+ handleSpeechDialog,
+ handleFetchPlugins,
+};
+export default connect(
+ mapStateToProps,
+ actionCreator
+)(withTranslation()(SpeechDialog as any) as any);
diff --git a/src/components/dialogs/speechDialog/interface.tsx b/src/components/dialogs/speechDialog/interface.tsx
new file mode 100644
index 00000000..807dee15
--- /dev/null
+++ b/src/components/dialogs/speechDialog/interface.tsx
@@ -0,0 +1,18 @@
+import BookModel from "../../../models/Book";
+import Plugin from "../../../models/Plugin";
+
+export interface SpeechDialogProps {
+ isSettingOpen: boolean;
+ isAboutOpen: boolean;
+ currentBook: BookModel;
+ handleSetting: (isSettingOpen: boolean) => void;
+ handleSpeechDialog: (isAboutOpen: boolean) => void;
+ handleFetchPlugins: () => void;
+ isAuthed: boolean;
+ t: (title: string) => string;
+ plugins: Plugin[];
+}
+export interface SpeechDialogState {
+ isShowExportAll: boolean;
+ isConvertPDF: boolean;
+}
diff --git a/src/components/dialogs/speechDialog/speechDialog.css b/src/components/dialogs/speechDialog/speechDialog.css
new file mode 100644
index 00000000..061fa596
--- /dev/null
+++ b/src/components/dialogs/speechDialog/speechDialog.css
@@ -0,0 +1,5 @@
+.icon-export-all {
+ line-height: 18px;
+ display: inline-block;
+ transform: rotate(-90deg) scale(0.7);
+}
diff --git a/src/pages/reader/component.tsx b/src/pages/reader/component.tsx
index 0687a992..4c6713c8 100644
--- a/src/pages/reader/component.tsx
+++ b/src/pages/reader/component.tsx
@@ -14,6 +14,7 @@ import DatabaseService from "../../utils/storage/databaseService";
import ConvertDialog from "../../components/dialogs/convertDialog";
import { isElectron } from "react-device-detect";
import SettingDialog from "../../components/dialogs/settingDialog";
+import SpeechDialog from "../../components/dialogs/speechDialog";
let lock = false; //prevent from clicking too fasts
let throttleTime =
@@ -200,43 +201,64 @@ class Reader extends React.Component {
{
- if (lock) return;
- lock = true;
- await this.props.htmlBook.rendition.next();
- this.handleLocation();
- setTimeout(() => (lock = false), throttleTime);
- }}
style={{
+ position: "absolute",
+ bottom: 10,
right: this.props.isSettingLocked ? 315 : 15,
+ display: "flex",
+ flexDirection: "column-reverse",
+ alignItems: "center",
+ gap: "8px",
+ zIndex: 10,
}}
>
-
+
{
+ if (lock) return;
+ lock = true;
+ await this.props.htmlBook.rendition.next();
+ this.handleLocation();
+ setTimeout(() => (lock = false), throttleTime);
+ }}
+ style={{ position: "static" }}
+ >
+
+
+
{
+ this.props.handleSpeechDialog(!this.props.isSpeechOpen);
+ }}
+ style={{ position: "static", transform: "rotate(0deg)" }}
+ >
+
+
+ {this.props.isAuthed &&
+ !this.props.isHideAIButton &&
+ ConfigService.getReaderConfig("isDisableAI") !== "yes" && (
+
{
+ this.props.handleMenuMode("assistant");
+ this.props.handleOriginalText(
+ await this.props.htmlBook.rendition.chapterText()
+ );
+ this.props.handleOpenMenu(true);
+ }}
+ style={{
+ position: "static",
+ transform: "rotate(0deg)",
+ fontWeight: "bold",
+ fontSize: "17px",
+ }}
+ >
+ AI
+
+ )}
- {this.props.isAuthed &&
- !this.props.isHideAIButton &&
- ConfigService.getReaderConfig("isDisableAI") !== "yes" && (
- {
- this.props.handleMenuMode("assistant");
- this.props.handleOriginalText(
- await this.props.htmlBook.rendition.chapterText()
- );
- this.props.handleOpenMenu(true);
- }}
- style={{
- bottom: "55px",
- transform: "rotate(0deg)",
- fontWeight: "bold",
- fontSize: "17px",
- right: this.props.isSettingLocked ? 315 : 15,
- }}
- >
- AI
-
- )}
>
)}
{
{this.props.currentBook.key && }
{this.props.isConvertOpen && }
+ {this.props.isSpeechOpen && }
);
}
diff --git a/src/pages/reader/index.css b/src/pages/reader/index.css
index a628acd6..b5978434 100644
--- a/src/pages/reader/index.css
+++ b/src/pages/reader/index.css
@@ -191,16 +191,13 @@
width: 40px;
height: 40px;
border-radius: 50%;
- position: absolute;
- bottom: 10px;
- right: 15px;
transform: rotate(-90deg);
cursor: pointer;
opacity: 0.2;
display: flex;
justify-content: center;
align-items: center;
- z-index: 10;
+ flex-shrink: 0;
}
.next-chapter-single-container:hover {
opacity: 0.7;
@@ -228,6 +225,10 @@
align-items: center;
justify-content: flex-end;
}
+.text-to-speech-icon {
+ font-size: 22px;
+ margin-right: 10px;
+}
.reader-setting-icon-container,
.reader-zoom-in-icon-container {
width: 50px;
diff --git a/src/pages/reader/index.tsx b/src/pages/reader/index.tsx
index 199083b9..adcea663 100644
--- a/src/pages/reader/index.tsx
+++ b/src/pages/reader/index.tsx
@@ -9,6 +9,7 @@ import {
handleOriginalText,
handleOpenMenu,
handleConvertDialog,
+ handleSpeechDialog,
handleScale,
handleFetchAuthed,
} from "../../store/actions";
@@ -25,6 +26,7 @@ const mapStateToProps = (state: stateType) => {
readerMode: state.reader.readerMode,
isNavLocked: state.reader.isNavLocked,
isConvertOpen: state.reader.isConvertOpen,
+ isSpeechOpen: state.reader.isSpeechOpen,
isSettingLocked: state.reader.isSettingLocked,
isAuthed: state.manager.isAuthed,
isSearch: state.manager.isSearch,
@@ -51,6 +53,7 @@ const actionCreator = {
handleConvertDialog,
handleScale,
handleFetchAuthed,
+ handleSpeechDialog,
};
export default connect(
mapStateToProps,
diff --git a/src/pages/reader/interface.tsx b/src/pages/reader/interface.tsx
index d741ac86..f803f26e 100644
--- a/src/pages/reader/interface.tsx
+++ b/src/pages/reader/interface.tsx
@@ -8,6 +8,7 @@ export interface ReaderProps {
isNavLocked: boolean;
isSettingLocked: boolean;
isConvertOpen: boolean;
+ isSpeechOpen: boolean;
isSearch: boolean;
isAuthed: boolean;
isHidePageButton: boolean;
@@ -21,6 +22,7 @@ export interface ReaderProps {
handleFetchNotes: () => void;
handleReaderMode: (readerMode: string) => void;
handleConvertDialog: (isConvertOpen: boolean) => void;
+ handleSpeechDialog: (isSpeechOpen: boolean) => void;
handleMenuMode: (menuMode: string) => void;
handleOriginalText: (originalText: string) => void;
handleFetchBooks: () => void;
diff --git a/src/store/actions/reader.tsx b/src/store/actions/reader.tsx
index 52495392..7c7e0c77 100644
--- a/src/store/actions/reader.tsx
+++ b/src/store/actions/reader.tsx
@@ -22,6 +22,9 @@ export function handleColor(color: number) {
export function handleConvertDialog(isConvertOpen: boolean) {
return { type: "HANDLE_CONVERT_DIALOG", payload: isConvertOpen };
}
+export function handleSpeechDialog(isSpeechOpen: boolean) {
+ return { type: "HANDLE_SPEECH_DIALOG", payload: isSpeechOpen };
+}
export function handleBookmarks(bookmarks: BookmarkModel[]) {
return { type: "HANDLE_BOOKMARKS", payload: bookmarks };
}
diff --git a/src/store/index.tsx b/src/store/index.tsx
index 504e7710..39847886 100644
--- a/src/store/index.tsx
+++ b/src/store/index.tsx
@@ -99,6 +99,7 @@ export type stateType = {
isNavLocked: boolean;
isSettingLocked: boolean;
isConvertOpen: boolean;
+ isSpeechOpen: boolean;
isHideFooter: boolean;
isHideHeader: boolean;
isHideBackground: boolean;
diff --git a/src/store/reducers/reader.tsx b/src/store/reducers/reader.tsx
index d0f1e164..b1252d2d 100644
--- a/src/store/reducers/reader.tsx
+++ b/src/store/reducers/reader.tsx
@@ -31,6 +31,7 @@ const initState = {
section: null,
readerMode: "double",
isConvertOpen: false,
+ isSpeechOpen: false,
isNavLocked: ConfigService.getReaderConfig("isNavLocked") === "yes",
isSettingLocked: ConfigService.getReaderConfig("isSettingLocked") === "yes",
isHideFooter: ConfigService.getReaderConfig("isHideFooter") === "yes",
@@ -75,6 +76,11 @@ export function reader(
...state,
isConvertOpen: action.payload,
};
+ case "HANDLE_SPEECH_DIALOG":
+ return {
+ ...state,
+ isSpeechOpen: action.payload,
+ };
case "HANDLE_CURRENT_CHAPTER_INDEX":
return {
...state,