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,