Various enhancements for mobile platforms (#188)

* Larger area to toggle header/footer bar

* Don't show rounded window on web platform

* Disable tooltips on mobile platforms as there is no mouse pointer

* Dedupe voices from web speech API
This commit is contained in:
Huang Xin
2025-01-17 15:42:16 +01:00
committed by GitHub
parent 5ca173818b
commit 58c907c7cc
15 changed files with 64 additions and 33 deletions
+7 -1
View File
@@ -1,5 +1,6 @@
'use client';
import clsx from 'clsx';
import * as React from 'react';
import { useState, useRef, useEffect, Suspense } from 'react';
import { useRouter } from 'next/navigation';
@@ -233,7 +234,12 @@ const LibraryPage = () => {
}
return (
<div className='library-page rounded-window bg-base-200/50 text-base-content flex h-dvh select-none flex-col overflow-hidden'>
<div
className={clsx(
'library-page bg-base-200/50 text-base-content flex h-dvh select-none flex-col overflow-hidden',
isTauriAppPlatform() && 'rounded-window',
)}
>
<div className='fixed top-0 z-40 w-full'>
<LibraryHeader
isSelectMode={isSelectMode}
@@ -4,6 +4,7 @@ import { useSettingsStore } from '@/store/settingsStore';
import { useReaderStore } from '@/store/readerStore';
import { useBookDataStore } from '@/store/bookDataStore';
import { useSidebarStore } from '@/store/sidebarStore';
import { isTauriAppPlatform } from '@/services/environment';
import FoliateViewer from './FoliateViewer';
import getGridTemplate from '@/utils/grid';
import SectionInfo from './SectionInfo';
@@ -59,7 +60,7 @@ const BooksGrid: React.FC<BooksGridProps> = ({ bookKeys, onCloseBook }) => {
<div
id={`gridcell-${bookKey}`}
key={bookKey}
className='rounded-window relative h-full w-full overflow-hidden'
className={`${isTauriAppPlatform() ? 'rounded-window' : ''} relative h-full w-full overflow-hidden`}
>
{isBookmarked && <Ribbon width={marginGap} />}
<HeaderBar
@@ -8,6 +8,7 @@ import { useReaderStore } from '@/store/readerStore';
import { useSidebarStore } from '@/store/sidebarStore';
import { useTranslation } from '@/hooks/useTranslation';
import { eventDispatcher } from '@/utils/event';
import { isTauriAppPlatform } from '@/services/environment';
import Button from '@/components/Button';
interface FooterBarProps {
@@ -60,8 +61,9 @@ const FooterBar: React.FC<FooterBarProps> = ({ bookKey, pageinfo, isHoveredAnim
<div
className={clsx(
'footer-bar absolute bottom-0 z-10 flex h-12 w-full items-center px-4',
'shadow-xs bg-base-100 rounded-window-bottom-right transition-opacity duration-300',
!isSideBarVisible && 'rounded-window-bottom-left',
'shadow-xs bg-base-100 transition-opacity duration-300',
isTauriAppPlatform() && 'rounded-window-bottom-right',
!isSideBarVisible && isTauriAppPlatform() && 'rounded-window-bottom-left',
isHoveredAnim && 'hover-bar-anim',
hoveredBookKey === bookKey ? `opacity-100` : `opacity-0`,
)}
@@ -5,6 +5,7 @@ import { PiDotsThreeVerticalBold } from 'react-icons/pi';
import { useEnv } from '@/context/EnvContext';
import { useReaderStore } from '@/store/readerStore';
import { useSidebarStore } from '@/store/sidebarStore';
import { isTauriAppPlatform } from '@/services/environment';
import useTrafficLight from '@/hooks/useTrafficLight';
import WindowButtons from '@/components/WindowButtons';
import Dropdown from '@/components/Dropdown';
@@ -49,8 +50,9 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
className={clsx(
`header-bar absolute top-0 z-10 flex h-11 w-full items-center pr-4`,
isTrafficLightVisible && isTopLeft && !isSideBarVisible ? 'pl-16' : 'pl-4',
`shadow-xs bg-base-100 rounded-window-top-right transition-opacity duration-300`,
!isSideBarVisible && 'rounded-window-top-left',
`shadow-xs bg-base-100 transition-opacity duration-300`,
isTauriAppPlatform() && 'rounded-window-top-right',
!isSideBarVisible && isTauriAppPlatform() && 'rounded-window-top-left',
isHoveredAnim && 'hover-bar-anim',
hoveredBookKey === bookKey || isDropdownOpen ? `visible` : `opacity-0`,
isDropdownOpen && 'header-bar-pinned',
@@ -1,11 +1,13 @@
'use client';
import clsx from 'clsx';
import * as React from 'react';
import { useEffect, Suspense, useRef } from 'react';
import { useEnv } from '@/context/EnvContext';
import { useLibraryStore } from '@/store/libraryStore';
import { useSettingsStore } from '@/store/settingsStore';
import { isTauriAppPlatform } from '@/services/environment';
import { AboutWindow } from '@/components/AboutWindow';
import { Toast } from '@/components/Toast';
import ReaderContent from './ReaderContent';
@@ -33,7 +35,12 @@ const Reader: React.FC<{ ids?: string }> = ({ ids }) => {
return (
library.length > 0 &&
settings.globalReadSettings && (
<div className='reader-page rounded-window bg-base-100 text-base-content select-none'>
<div
className={clsx(
`reader-page bg-base-100 text-base-content select-none`,
isTauriAppPlatform() && 'rounded-window',
)}
>
<Suspense>
<ReaderContent ids={ids} settings={settings} />
<AboutWindow />
@@ -15,7 +15,7 @@ const PopupButton: React.FC<PopupButtonProps> = ({ showTooltip, tooltipText, Ico
};
return (
<div
className='tooltip tooltip-bottom'
className='lg:tooltip lg:tooltip-bottom'
data-tip={!buttonClicked && showTooltip ? tooltipText : null}
>
<button
@@ -12,6 +12,7 @@ import { TextSelection } from '@/utils/sel';
import { BookNote } from '@/types/book';
import { uniqueId } from '@/utils/misc';
import { eventDispatcher } from '@/utils/event';
import { isTauriAppPlatform } from '@/services/environment';
import useDragBar from '../../hooks/useDragBar';
import BooknoteItem from '../sidebar/BooknoteItem';
import NotebookHeader from './Header';
@@ -140,7 +141,7 @@ const Notebook: React.FC = ({}) => {
<div
className={clsx(
'notebook-container bg-base-200 right-0 z-20 h-full min-w-60 select-none',
'rounded-window-top-right rounded-window-bottom-right',
isTauriAppPlatform() && 'rounded-window-top-right rounded-window-bottom-right',
!isNotebookPinned && 'shadow-2xl',
)}
style={{
@@ -28,7 +28,7 @@ const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
<div className='flex items-center justify-between'>
<h2 className='font-medium'>{_('Theme Mode')}</h2>
<div className='flex gap-2'>
<div className='tooltip tooltip-bottom' data-tip={_('Auto Mode')}>
<div className='lg:tooltip lg:tooltip-bottom' data-tip={_('Auto Mode')}>
<button
className={`btn btn-ghost btn-circle ${themeMode === 'auto' ? 'btn-active bg-base-300' : ''}`}
onClick={() => updateThemeMode('auto')}
@@ -37,7 +37,7 @@ const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
</button>
</div>
<div className='tooltip tooltip-bottom' data-tip={_('Light Mode')}>
<div className='lg:tooltip lg:tooltip-bottom' data-tip={_('Light Mode')}>
<button
className={`btn btn-ghost btn-circle ${themeMode === 'light' ? 'btn-active bg-base-300' : ''}`}
onClick={() => updateThemeMode('light')}
@@ -46,7 +46,7 @@ const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
</button>
</div>
<div className='tooltip tooltip-bottom' data-tip={_('Dark Mode')}>
<div className='lg:tooltip lg:tooltip-bottom' data-tip={_('Dark Mode')}>
<button
className={`btn btn-ghost btn-circle ${themeMode === 'dark' ? 'btn-active bg-base-300' : ''}`}
onClick={() => updateThemeMode('dark')}
@@ -29,7 +29,7 @@ const DialogMenu: React.FC<DialogMenuProps> = ({ toggleDropdown }) => {
<span style={{ minWidth: '20px' }}>
{isFontLayoutSettingsGlobal && <MdCheck size={20} className='text-base-content' />}
</span>
<div className='tooltip' data-tip='Uncheck for current book settings'>
<div className='lg:tooltip' data-tip={_('Uncheck for current book settings')}>
<span className='ml-2'>{_('Global Settings')}</span>
</div>
</div>
@@ -148,7 +148,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
<div className='flex items-center justify-between'>
<h2 className='font-medium'>{_('Writing Mode')}</h2>
<div className='flex gap-2'>
<div className='tooltip tooltip-bottom' data-tip={_('Default')}>
<div className='lg:tooltip lg:tooltip-bottom' data-tip={_('Default')}>
<button
className={`btn btn-ghost btn-circle ${writingMode === 'auto' ? 'btn-active bg-base-300' : ''}`}
onClick={() => setWritingMode('auto')}
@@ -157,7 +157,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
</button>
</div>
<div className='tooltip tooltip-bottom' data-tip={_('Horizontal Direction')}>
<div className='lg:tooltip lg:tooltip-bottom' data-tip={_('Horizontal Direction')}>
<button
className={`btn btn-ghost btn-circle ${writingMode === 'horizontal-tb' ? 'btn-active bg-base-300' : ''}`}
onClick={() => setWritingMode('horizontal-tb')}
@@ -166,7 +166,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
</button>
</div>
<div className='tooltip tooltip-bottom' data-tip={_('Vertical Direction')}>
<div className='lg:tooltip lg:tooltip-bottom' data-tip={_('Vertical Direction')}>
<button
className={`btn btn-ghost btn-circle ${writingMode === 'vertical-rl' ? 'btn-active bg-base-300' : ''}`}
onClick={() => setWritingMode('vertical-rl')}
@@ -7,6 +7,7 @@ import { useReaderStore } from '@/store/readerStore';
import { useSidebarStore } from '@/store/sidebarStore';
import { BookSearchResult } from '@/types/book';
import { eventDispatcher } from '@/utils/event';
import { isTauriAppPlatform } from '@/services/environment';
import SidebarHeader from './Header';
import SidebarContent from './Content';
import BookCard from './BookCard';
@@ -105,8 +106,8 @@ const SideBar: React.FC<{
<>
<div
className={clsx(
'sidebar-container bg-base-200 z-20 flex h-full min-w-60 flex-col',
'rounded-window-top-left rounded-window-bottom-left select-none',
'sidebar-container bg-base-200 z-20 flex h-full min-w-60 select-none flex-col',
isTauriAppPlatform() && 'rounded-window-top-left rounded-window-bottom-left',
!isSideBarPinned && 'shadow-2xl',
)}
style={{
@@ -26,7 +26,7 @@ const TabNavigation: React.FC<{
{tabs.map((tab) => (
<div
key={tab}
className='tooltip tooltip-top z-50 m-1.5 flex-1 cursor-pointer rounded-md p-2'
className='lg:tooltip lg:tooltip-top z-50 m-1.5 flex-1 cursor-pointer rounded-md p-2'
data-tip={
tab === 'toc'
? _('Table of Contents')
@@ -29,14 +29,7 @@ export const useClickEvent = (
if (!consumed) {
const centerStartX = rect.left + rect.width * 0.375;
const centerEndX = rect.left + rect.width * 0.625;
const centerStartY = rect.top + rect.height * 0.375;
const centerEndY = rect.top + rect.height * 0.625;
if (
screenX >= centerStartX &&
screenX <= centerEndX &&
screenY >= centerStartY &&
screenY <= centerEndY
) {
if (screenX >= centerStartX && screenX <= centerEndX) {
// toggle visibility of the header bar and the footer bar
setHoveredBookKey(hoveredBookKey ? '' : bookKey);
} else if (screenX >= rect.left + rect.width / 2) {
+7 -3
View File
@@ -20,9 +20,13 @@ const Button: React.FC<ButtonProps> = ({
}) => {
return (
<div
className={clsx('tooltip z-50 h-8 min-h-8 w-8', tooltip && `tooltip-${tooltipDirection}`, {
'tooltip-hidden': !tooltip,
})}
className={clsx(
'lg:tooltip z-50 h-8 min-h-8 w-8',
tooltip && `lg:tooltip-${tooltipDirection}`,
{
'tooltip-hidden': !tooltip,
},
)}
data-tip={tooltip}
>
<button
@@ -297,9 +297,23 @@ export class WebSpeechClient implements TTSClient {
.filter((voice) => voice.lang.startsWith(locale))
.filter((voice) => isValidVoice(voice.voiceURI || ''))
.filter(isNotBlacklisted);
const voices = filteredVoices.map((voice) => {
return { id: voice.voiceURI, name: voice.name, lang: voice.lang } as TTSVoice;
});
const seenIds = new Set<string>();
const voices = filteredVoices
.map(
(voice) =>
({
id: voice.voiceURI,
name: voice.name,
lang: voice.lang,
}) as TTSVoice,
)
.filter((voice) => {
if (seenIds.has(voice.id)) {
return false;
}
seenIds.add(voice.id);
return true;
});
voices.forEach((voice) => {
voice.disabled = !this.available;
});