forked from akai/readest
ui: various UI fixes and enhancements on mobile platforms (#1113)
This commit is contained in:
@@ -305,5 +305,7 @@
|
||||
"Landscape": "الأفقي",
|
||||
"Open Last Book on Start": "فتح آخر كتاب عند البدء",
|
||||
"Checking for updates...": "جارٍ التحقق من التحديثات...",
|
||||
"Error checking for updates": "خطأ في التحقق من التحديثات"
|
||||
"Error checking for updates": "خطأ في التحقق من التحديثات",
|
||||
"Details": "التفاصيل",
|
||||
"Cancel Selection": "إلغاء التحديد"
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
"Book downloaded: {{title}}": "Buch heruntergeladen: {{title}}",
|
||||
"Failed to download book: {{title}}": "Fehler beim Herunterladen des Buches: {{title}}",
|
||||
"Upload Book": "Buch hochladen",
|
||||
"Auto Upload Books to Cloud": "Bücher automatisch in die Cloud hochladen",
|
||||
"Auto Upload Books to Cloud": "Bücher automatisch hochladen",
|
||||
"{{percentage}}% of Cloud Storage Used.": "{{percentage}}% des Cloud-Speichers verwendet.",
|
||||
"Storage": "Speicher",
|
||||
"Book deleted: {{title}}": "Buch gelöscht: {{title}}",
|
||||
@@ -305,5 +305,7 @@
|
||||
"Landscape": "Querformat",
|
||||
"Open Last Book on Start": "Letztes Buch beim Start öffnen",
|
||||
"Checking for updates...": "Auf Updates prüfen...",
|
||||
"Error checking for updates": "Fehler beim Überprüfen auf Updates"
|
||||
"Error checking for updates": "Fehler beim Überprüfen auf Updates",
|
||||
"Details": "Details",
|
||||
"Cancel Selection": "Auswahl abbrechen"
|
||||
}
|
||||
|
||||
@@ -134,12 +134,12 @@
|
||||
"Book downloaded: {{title}}": "Το βιβλίο με τίτλο {{title}} κατέβηκε",
|
||||
"Failed to download book: {{title}}": "Αποτυχία λήψης βιβλίου: {{title}}",
|
||||
"Upload Book": "Ανέβασμα βιβλίου",
|
||||
"Auto Upload Books to Cloud": "Αυτόματο ανέβασμα βιβλίων στο cloud",
|
||||
"Auto Upload Books to Cloud": "Αυτόματη αποθήκευση στο cloud",
|
||||
"{{percentage}}% of Cloud Storage Used.": "{{percentage}}% της αποθήκευσης στο cloud χρησιμοποιήθηκε.",
|
||||
"Storage": "Αποθήκευση",
|
||||
"Book deleted: {{title}}": "Το βιβλίο με τίτλο {{title}} διαγράφηκε",
|
||||
"Failed to delete book: {{title}}": "Αποτυχία διαγραφής βιβλίου: {{title}}",
|
||||
"Check Updates on Start": "Έλεγχος ενημερώσεων κατά την εκκίνηση",
|
||||
"Check Updates on Start": "Αυτόματος έλεγχος ενημερώσεων",
|
||||
"Insufficient storage quota": "Ανεπαρκές όριο αποθήκευσης",
|
||||
"Font Weight": "Βάρος γραμματοσειράς",
|
||||
"Line Spacing": "Διάκενο γραμμών",
|
||||
@@ -296,14 +296,16 @@
|
||||
"Grid": "Πλέγμα",
|
||||
"(from 'As You Like It', Act II)": "('Όπως σας αρέσει', Πράξη II)",
|
||||
"Link Color": "Χρώμα συνδέσμου",
|
||||
"Volume Keys for Page Flip": "Πλήκτρα έντασης για ανατροπή σελίδας",
|
||||
"Volume Keys for Page Flip": "Ανατροπή με πλήκτρα έντασης",
|
||||
"Clicks for Page Flip": "Κλικ για ανατροπή σελίδας",
|
||||
"Swap Clicks Area": "Ανταλλαγή περιοχής κλικ",
|
||||
"Screen": "Οθόνη",
|
||||
"Orientation": "Προσανατολισμός",
|
||||
"Portrait": "Πορτραίτο",
|
||||
"Landscape": "Τοπίο",
|
||||
"Open Last Book on Start": "Άνοιγμα τελευταίου βιβλίου κατά την εκκίνηση",
|
||||
"Open Last Book on Start": "Άνοιγμα τελευταίου βιβλίου",
|
||||
"Checking for updates...": "Έλεγχος για ενημερώσεις...",
|
||||
"Error checking for updates": "Σφάλμα κατά τον έλεγχο για ενημερώσεις"
|
||||
"Error checking for updates": "Σφάλμα κατά τον έλεγχο για ενημερώσεις",
|
||||
"Details": "Πληρ.",
|
||||
"Cancel Selection": "Ακύρωση επιλογής"
|
||||
}
|
||||
|
||||
@@ -305,5 +305,7 @@
|
||||
"Landscape": "Horizontal",
|
||||
"Open Last Book on Start": "Abrir último libro al iniciar",
|
||||
"Checking for updates...": "Comprobando actualizaciones...",
|
||||
"Error checking for updates": "Error al comprobar actualizaciones"
|
||||
"Error checking for updates": "Error al comprobar actualizaciones",
|
||||
"Details": "Detalles",
|
||||
"Cancel Selection": "Cancelar selección"
|
||||
}
|
||||
|
||||
@@ -305,5 +305,7 @@
|
||||
"Landscape": "Paysage",
|
||||
"Open Last Book on Start": "Ouvrir le dernier livre au démarrage",
|
||||
"Checking for updates...": "Vérification des mises à jour...",
|
||||
"Error checking for updates": "Erreur lors de la vérification des mises à jour"
|
||||
"Error checking for updates": "Erreur lors de la vérification des mises à jour",
|
||||
"Details": "Détails",
|
||||
"Cancel Selection": "Annuler la sélection"
|
||||
}
|
||||
|
||||
@@ -305,5 +305,7 @@
|
||||
"Landscape": "लैंडस्केप",
|
||||
"Open Last Book on Start": "शुरू में अंतिम पुस्तक खोलें",
|
||||
"Checking for updates...": "अपडेट की जांच कर रहा है...",
|
||||
"Error checking for updates": "अपडेट की जांच करते समय त्रुटि"
|
||||
"Error checking for updates": "अपडेट की जांच करते समय त्रुटि",
|
||||
"Details": "विवरण",
|
||||
"Cancel Selection": "चयन रद्द करें"
|
||||
}
|
||||
|
||||
@@ -305,5 +305,7 @@
|
||||
"Landscape": "Lanskap",
|
||||
"Open Last Book on Start": "Buka Buku Terakhir saat Memulai",
|
||||
"Checking for updates...": "Memeriksa pembaruan...",
|
||||
"Error checking for updates": "Kesalahan saat memeriksa pembaruan"
|
||||
"Error checking for updates": "Kesalahan saat memeriksa pembaruan",
|
||||
"Details": "Detail",
|
||||
"Cancel Selection": "Batalkan Pilihan"
|
||||
}
|
||||
|
||||
@@ -305,5 +305,7 @@
|
||||
"Landscape": "Paesaggio",
|
||||
"Open Last Book on Start": "Apri ultimo libro all'avvio",
|
||||
"Checking for updates...": "Controllo aggiornamenti...",
|
||||
"Error checking for updates": "Errore durante il controllo degli aggiornamenti"
|
||||
"Error checking for updates": "Errore durante il controllo degli aggiornamenti",
|
||||
"Details": "Dettagli",
|
||||
"Cancel Selection": "Annulla selezione"
|
||||
}
|
||||
|
||||
@@ -305,5 +305,7 @@
|
||||
"Landscape": "横",
|
||||
"Open Last Book on Start": "開始時に最後の書籍を開く",
|
||||
"Checking for updates...": "更新を確認中...",
|
||||
"Error checking for updates": "更新の確認中にエラーが発生しました"
|
||||
"Error checking for updates": "更新の確認中にエラーが発生しました",
|
||||
"Details": "詳細",
|
||||
"Cancel Selection": "選択をキャンセル"
|
||||
}
|
||||
|
||||
@@ -305,5 +305,7 @@
|
||||
"Landscape": "가로",
|
||||
"Open Last Book on Start": "시작 시 마지막 책 열기",
|
||||
"Checking for updates...": "업데이트 확인 중...",
|
||||
"Error checking for updates": "업데이트 확인 중 오류 발생"
|
||||
"Error checking for updates": "업데이트 확인 중 오류 발생",
|
||||
"Details": "세부정보",
|
||||
"Cancel Selection": "선택 취소"
|
||||
}
|
||||
|
||||
@@ -305,5 +305,7 @@
|
||||
"Landscape": "Krajobraz",
|
||||
"Open Last Book on Start": "Otwórz ostatnią książkę przy starcie",
|
||||
"Checking for updates...": "Sprawdzanie aktualizacji...",
|
||||
"Error checking for updates": "Błąd podczas sprawdzania aktualizacji"
|
||||
"Error checking for updates": "Błąd podczas sprawdzania aktualizacji",
|
||||
"Details": "Szczegóły",
|
||||
"Cancel Selection": "Anuluj zaznaczenie"
|
||||
}
|
||||
|
||||
@@ -305,5 +305,7 @@
|
||||
"Landscape": "Paisagem",
|
||||
"Open Last Book on Start": "Abrir o Último Livro ao Iniciar",
|
||||
"Checking for updates...": "Verificando atualizações...",
|
||||
"Error checking for updates": "Erro ao verificar atualizações"
|
||||
"Error checking for updates": "Erro ao verificar atualizações",
|
||||
"Details": "Detalhes",
|
||||
"Cancel Selection": "Cancelar Seleção"
|
||||
}
|
||||
|
||||
@@ -305,5 +305,7 @@
|
||||
"Landscape": "Ландшафт",
|
||||
"Open Last Book on Start": "Открыть последнюю книгу при запуске",
|
||||
"Checking for updates...": "Проверка обновлений...",
|
||||
"Error checking for updates": "Ошибка при проверке обновлений"
|
||||
"Error checking for updates": "Ошибка при проверке обновлений",
|
||||
"Details": "Инфо",
|
||||
"Cancel Selection": "Отменить выбор"
|
||||
}
|
||||
|
||||
@@ -305,5 +305,7 @@
|
||||
"Landscape": "Yatay",
|
||||
"Open Last Book on Start": "Açılışta Son Kitabı Aç",
|
||||
"Checking for updates...": "Yenilikler kontrol ediliyor...",
|
||||
"Error checking for updates": "Güncellemeleri kontrol ederken hata oluştu"
|
||||
"Error checking for updates": "Güncellemeleri kontrol ederken hata oluştu",
|
||||
"Details": "Detaylar",
|
||||
"Cancel Selection": "Işaretlemeyi İptal Et"
|
||||
}
|
||||
|
||||
@@ -305,5 +305,7 @@
|
||||
"Landscape": "Ландшафт",
|
||||
"Open Last Book on Start": "Відкрити останню книгу при запуску",
|
||||
"Checking for updates...": "Перевірка наявності оновлень...",
|
||||
"Error checking for updates": "Помилка перевірки наявності оновлень"
|
||||
"Error checking for updates": "Помилка перевірки наявності оновлень",
|
||||
"Details": "Деталі",
|
||||
"Cancel Selection": "Скасувати вибір"
|
||||
}
|
||||
|
||||
@@ -305,5 +305,7 @@
|
||||
"Landscape": "Phong cảnh",
|
||||
"Open Last Book on Start": "Mở sách cuối cùng khi khởi động",
|
||||
"Checking for updates...": "Đang kiểm tra cập nhật...",
|
||||
"Error checking for updates": "Lỗi khi kiểm tra cập nhật"
|
||||
"Error checking for updates": "Lỗi khi kiểm tra cập nhật",
|
||||
"Details": "Chi tiết",
|
||||
"Cancel Selection": "Hủy chọn"
|
||||
}
|
||||
|
||||
@@ -305,5 +305,7 @@
|
||||
"Landscape": "横屏",
|
||||
"Open Last Book on Start": "启动时打开上次阅读的书籍",
|
||||
"Checking for updates...": "检查更新中...",
|
||||
"Error checking for updates": "检查更新时出错"
|
||||
"Error checking for updates": "检查更新时出错",
|
||||
"Details": "详情",
|
||||
"Cancel Selection": "取消选择"
|
||||
}
|
||||
|
||||
@@ -305,5 +305,7 @@
|
||||
"Landscape": "橫屏",
|
||||
"Open Last Book on Start": "啟動時打開上次閱讀的書籍",
|
||||
"Checking for updates...": "檢查更新中...",
|
||||
"Error checking for updates": "檢查更新時出錯"
|
||||
"Error checking for updates": "檢查更新時出錯",
|
||||
"Details": "詳情",
|
||||
"Cancel Selection": "取消選擇"
|
||||
}
|
||||
|
||||
@@ -78,8 +78,8 @@ export default function AuthPage() {
|
||||
useTheme({ systemUIVisible: false });
|
||||
|
||||
const getTauriRedirectTo = (isOAuth: boolean) => {
|
||||
if (process.env.NODE_ENV === 'production' || appService?.isMobile || USE_APPLE_SIGN_IN) {
|
||||
if (appService?.isMobile) {
|
||||
if (process.env.NODE_ENV === 'production' || appService?.isMobileApp || USE_APPLE_SIGN_IN) {
|
||||
if (appService?.isMobileApp) {
|
||||
return isOAuth ? DEEPLINK_CALLBACK : WEB_AUTH_CALLBACK;
|
||||
}
|
||||
return DEEPLINK_CALLBACK;
|
||||
@@ -172,7 +172,7 @@ export default function AuthPage() {
|
||||
|
||||
const startTauriOAuth = async () => {
|
||||
try {
|
||||
if (process.env.NODE_ENV === 'production' || appService?.isMobile || USE_APPLE_SIGN_IN) {
|
||||
if (process.env.NODE_ENV === 'production' || appService?.isMobileApp || USE_APPLE_SIGN_IN) {
|
||||
const { getCurrentWindow } = await import('@tauri-apps/api/window');
|
||||
const currentWindow = getCurrentWindow();
|
||||
currentWindow.listen('single-instance', ({ event, payload }) => {
|
||||
|
||||
@@ -100,7 +100,7 @@ const BookItem: React.FC<BookItemProps> = ({
|
||||
{transferProgress !== null ? (
|
||||
transferProgress === 100 ? null : (
|
||||
<div
|
||||
className='radial-progress opacity-0 group-hover:opacity-100'
|
||||
className='radial-progress sm:opacity-0 sm:group-hover:opacity-100'
|
||||
style={
|
||||
{
|
||||
'--value': transferProgress,
|
||||
@@ -113,7 +113,7 @@ const BookItem: React.FC<BookItemProps> = ({
|
||||
)
|
||||
) : (
|
||||
<button
|
||||
className='show-detail-button opacity-0 group-hover:opacity-100'
|
||||
className='show-detail-button -m-2 p-2 sm:opacity-0 sm:group-hover:opacity-100'
|
||||
onPointerDown={(e) => stopEvent(e)}
|
||||
onPointerUp={(e) => stopEvent(e)}
|
||||
onPointerMove={(e) => stopEvent(e)}
|
||||
@@ -133,17 +133,19 @@ const BookItem: React.FC<BookItemProps> = ({
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className='show-detail-button opacity-0 group-hover:opacity-100'
|
||||
onPointerDown={(e) => stopEvent(e)}
|
||||
onPointerUp={(e) => stopEvent(e)}
|
||||
onPointerMove={(e) => stopEvent(e)}
|
||||
onPointerCancel={(e) => stopEvent(e)}
|
||||
onPointerLeave={(e) => stopEvent(e)}
|
||||
onClick={() => showBookDetailsModal(book)}
|
||||
>
|
||||
<CiCircleMore size={iconSize15} />
|
||||
</button>
|
||||
{!appService?.isMobile && (
|
||||
<button
|
||||
className='show-detail-button -m-2 p-2 sm:opacity-0 sm:group-hover:opacity-100'
|
||||
onPointerDown={(e) => stopEvent(e)}
|
||||
onPointerUp={(e) => stopEvent(e)}
|
||||
onPointerMove={(e) => stopEvent(e)}
|
||||
onPointerCancel={(e) => stopEvent(e)}
|
||||
onPointerLeave={(e) => stopEvent(e)}
|
||||
onClick={() => showBookDetailsModal(book)}
|
||||
>
|
||||
<CiCircleMore size={iconSize15} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import clsx from 'clsx';
|
||||
import * as React from 'react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { MdDelete, MdOpenInNew, MdOutlineCancel } from 'react-icons/md';
|
||||
import { MdDelete, MdOpenInNew, MdOutlineCancel, MdInfoOutline } from 'react-icons/md';
|
||||
import { LuFolderPlus } from 'react-icons/lu';
|
||||
import { PiPlus } from 'react-icons/pi';
|
||||
import { Book, BooksGroup } from '@/types/book';
|
||||
@@ -162,6 +162,13 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
navigateToReader(router, selectedBooks);
|
||||
};
|
||||
|
||||
const openBookDetails = () => {
|
||||
const book = libraryBooks.find((book) => book.hash === selectedBooks[0]);
|
||||
if (book) {
|
||||
handleShowDetailsBook(book);
|
||||
}
|
||||
};
|
||||
|
||||
const confirmDelete = async () => {
|
||||
selectedBooks.forEach((id) => {
|
||||
for (const book of libraryBooks.filter((book) => book.hash === id || book.groupId === id)) {
|
||||
@@ -290,14 +297,14 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
<div
|
||||
className={clsx(
|
||||
'flex items-center justify-center shadow-lg',
|
||||
'text-base-content bg-base-300 text-sm',
|
||||
'bg-gray-600 text-xs text-white',
|
||||
'mx-auto w-fit space-x-6 rounded-lg p-4',
|
||||
)}
|
||||
>
|
||||
<button
|
||||
onClick={openSelectedBooks}
|
||||
className={clsx(
|
||||
'flex flex-col items-center justify-center',
|
||||
'flex flex-col items-center justify-center gap-1',
|
||||
(!selectedBooks.length || !selectedBooks.every((id) => isMd5(id))) &&
|
||||
'btn-disabled opacity-50',
|
||||
)}
|
||||
@@ -308,30 +315,43 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
<button
|
||||
onClick={groupSelectedBooks}
|
||||
className={clsx(
|
||||
'flex flex-col items-center justify-center',
|
||||
'flex flex-col items-center justify-center gap-1',
|
||||
!selectedBooks.length && 'btn-disabled opacity-50',
|
||||
)}
|
||||
>
|
||||
<LuFolderPlus />
|
||||
<div>{_('Group')}</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={openBookDetails}
|
||||
className={clsx(
|
||||
'flex flex-col items-center justify-center gap-1',
|
||||
(selectedBooks.length !== 1 || !selectedBooks.every((id) => isMd5(id))) &&
|
||||
'btn-disabled opacity-50',
|
||||
)}
|
||||
>
|
||||
<MdInfoOutline />
|
||||
<div>{_('Details')}</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={deleteSelectedBooks}
|
||||
className={clsx(
|
||||
'flex flex-col items-center justify-center',
|
||||
'flex flex-col items-center justify-center gap-1',
|
||||
!selectedBooks.length && 'btn-disabled opacity-50',
|
||||
)}
|
||||
>
|
||||
<MdDelete className='fill-red-500' />
|
||||
<div className='text-red-500'>{_('Delete')}</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleSetSelectMode(false)}
|
||||
className={clsx('flex flex-col items-center justify-center')}
|
||||
>
|
||||
<MdOutlineCancel />
|
||||
<div>{_('Cancel')}</div>
|
||||
</button>
|
||||
{!appService?.isMobile && (
|
||||
<button
|
||||
onClick={() => handleSetSelectMode(false)}
|
||||
className={clsx('flex flex-col items-center justify-center gap-1')}
|
||||
>
|
||||
<MdOutlineCancel />
|
||||
<div>{_('Cancel')}</div>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import clsx from 'clsx';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import MenuItem from '@/components/MenuItem';
|
||||
|
||||
@@ -8,6 +10,7 @@ interface ImportMenuProps {
|
||||
|
||||
const ImportMenu: React.FC<ImportMenuProps> = ({ setIsDropdownOpen, onImportBooks }) => {
|
||||
const _ = useTranslation();
|
||||
const { appService } = useEnv();
|
||||
|
||||
const handleImportBooks = () => {
|
||||
onImportBooks();
|
||||
@@ -17,7 +20,10 @@ const ImportMenu: React.FC<ImportMenuProps> = ({ setIsDropdownOpen, onImportBook
|
||||
return (
|
||||
<ul
|
||||
tabIndex={-1}
|
||||
className='dropdown-content dropdown-center bg-base-100 menu rounded-box z-[1] mt-3 w-52 p-2 shadow'
|
||||
className={clsx(
|
||||
'dropdown-content bg-base-100 menu rounded-box z-[1] mt-3 w-52 p-2 shadow',
|
||||
appService?.isMobile ? 'no-triangle' : 'dropdown-center',
|
||||
)}
|
||||
>
|
||||
<MenuItem label={_('From Local File')} onClick={handleImportBooks} />
|
||||
</ul>
|
||||
|
||||
@@ -142,7 +142,7 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
setSearchQuery('');
|
||||
throttledUpdateQueryParam('');
|
||||
}}
|
||||
className='text-gray-400 hover:text-gray-600'
|
||||
className='pe-1 text-gray-400 hover:text-gray-600'
|
||||
aria-label={_('Clear Search')}
|
||||
>
|
||||
<IoMdCloseCircle className='h-4 w-4' />
|
||||
@@ -150,7 +150,10 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
)}
|
||||
<span className='bg-base-content/50 mx-2 h-4 w-[0.5px]'></span>
|
||||
<Dropdown
|
||||
className='exclude-title-bar-mousedown dropdown-bottom flex h-6 cursor-pointer justify-center'
|
||||
className={clsx(
|
||||
'exclude-title-bar-mousedown dropdown-bottom flex h-6 cursor-pointer justify-center',
|
||||
appService?.isMobile ? 'dropdown-end' : 'dropdown-center',
|
||||
)}
|
||||
buttonClassName='p-0 h-6 min-h-6 w-6 flex items-center justify-center'
|
||||
toggleButton={
|
||||
<div className='lg:tooltip lg:tooltip-bottom' data-tip={_('Import Books')}>
|
||||
@@ -160,47 +163,61 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
>
|
||||
<ImportMenu onImportBooks={onImportBooks} />
|
||||
</Dropdown>
|
||||
<button
|
||||
onClick={onToggleSelectMode}
|
||||
aria-label={_('Select Multiple Books')}
|
||||
className='h-6'
|
||||
>
|
||||
<div
|
||||
className='lg:tooltip lg:tooltip-bottom cursor-pointer'
|
||||
data-tip={_('Select Books')}
|
||||
{appService?.isMobile ? null : (
|
||||
<button
|
||||
onClick={onToggleSelectMode}
|
||||
aria-label={_('Select Multiple Books')}
|
||||
className='h-6'
|
||||
>
|
||||
<PiSelectionAllDuotone
|
||||
role='button'
|
||||
className={`h-6 w-6 ${isSelectMode ? 'fill-gray-400' : 'fill-gray-500'}`}
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
className='lg:tooltip lg:tooltip-bottom cursor-pointer'
|
||||
data-tip={_('Select Books')}
|
||||
>
|
||||
<PiSelectionAllDuotone
|
||||
role='button'
|
||||
className={`h-6 w-6 ${isSelectMode ? 'fill-gray-400' : 'fill-gray-500'}`}
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex h-full items-center gap-x-2 sm:gap-x-4'>
|
||||
<Dropdown
|
||||
className='exclude-title-bar-mousedown dropdown-bottom dropdown-end'
|
||||
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
|
||||
toggleButton={<PiDotsThreeCircle size={iconSize18} />}
|
||||
>
|
||||
<SortMenu />
|
||||
</Dropdown>
|
||||
<Dropdown
|
||||
className='exclude-title-bar-mousedown dropdown-bottom dropdown-end'
|
||||
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
|
||||
toggleButton={<MdOutlineMenu size={iconSize18} />}
|
||||
>
|
||||
<SettingsMenu />
|
||||
</Dropdown>
|
||||
{appService?.hasWindowBar && (
|
||||
<WindowButtons
|
||||
headerRef={headerRef}
|
||||
showMinimize={windowButtonVisible}
|
||||
showMaximize={windowButtonVisible}
|
||||
showClose={windowButtonVisible}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{appService?.isMobile && isSelectMode ? (
|
||||
<div className='w-max-[72px] w-min-[72px] flex h-full items-center'>
|
||||
<button
|
||||
onClick={onToggleSelectMode}
|
||||
className='btn btn-ghost text-base-content/85 h-8 min-h-8 w-[72px] p-0'
|
||||
aria-label={_('Cancel Selection')}
|
||||
>
|
||||
{_('Cancel')}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className='flex h-full items-center gap-x-2 sm:gap-x-4'>
|
||||
<Dropdown
|
||||
className='exclude-title-bar-mousedown dropdown-bottom dropdown-end'
|
||||
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
|
||||
toggleButton={<PiDotsThreeCircle size={iconSize18} />}
|
||||
>
|
||||
<SortMenu />
|
||||
</Dropdown>
|
||||
<Dropdown
|
||||
className='exclude-title-bar-mousedown dropdown-bottom dropdown-end'
|
||||
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
|
||||
toggleButton={<MdOutlineMenu size={iconSize18} />}
|
||||
>
|
||||
<SettingsMenu />
|
||||
</Dropdown>
|
||||
{appService?.hasWindowBar && (
|
||||
<WindowButtons
|
||||
headerRef={headerRef}
|
||||
showMinimize={windowButtonVisible}
|
||||
showMaximize={windowButtonVisible}
|
||||
showClose={windowButtonVisible}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { PiUserCircle } from 'react-icons/pi';
|
||||
@@ -142,7 +143,10 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
return (
|
||||
<div
|
||||
tabIndex={0}
|
||||
className='settings-menu dropdown-content no-triangle border-base-100 z-20 mt-2 shadow-2xl'
|
||||
className={clsx(
|
||||
'settings-menu dropdown-content no-triangle border-base-100',
|
||||
'z-20 mt-2 max-w-[90vw] shadow-2xl',
|
||||
)}
|
||||
>
|
||||
{user ? (
|
||||
<MenuItem
|
||||
|
||||
@@ -94,7 +94,7 @@ const Reader: React.FC<{ ids?: string }> = ({ ids }) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!appService?.isMobile) return;
|
||||
if (!appService?.isMobileApp) return;
|
||||
const systemUIVisible = !!hoveredBookKey;
|
||||
setSystemUIVisibility({ visible: systemUIVisible, darkMode: isDarkMode });
|
||||
if (systemUIVisible) {
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import React from 'react';
|
||||
import { RiFontSize } from 'react-icons/ri';
|
||||
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import Button from '@/components/Button';
|
||||
|
||||
const SettingsToggler = () => {
|
||||
const _ = useTranslation();
|
||||
const { setHoveredBookKey } = useReaderStore();
|
||||
const { isFontLayoutSettingsDialogOpen, setFontLayoutSettingsDialogOpen } = useSettingsStore();
|
||||
const handleToggleSettings = () => {
|
||||
setHoveredBookKey('');
|
||||
setFontLayoutSettingsDialogOpen(!isFontLayoutSettingsDialogOpen);
|
||||
};
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { BookConfig } from '@/types/book';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { RiFontSize } from 'react-icons/ri';
|
||||
@@ -28,7 +29,10 @@ type TabConfig = {
|
||||
|
||||
const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ bookKey }) => {
|
||||
const _ = useTranslation();
|
||||
const { appService } = useEnv();
|
||||
const [isRtl] = useState(() => getDirFromUILanguage() === 'rtl');
|
||||
const tabsRef = useRef<HTMLDivElement | null>(null);
|
||||
const [showTabLabels, setShowTabLabels] = useState(false);
|
||||
const [activePanel, setActivePanel] = useState<SettingsPanelType>(
|
||||
(localStorage.getItem('lastConfigPanel') || 'Font') as SettingsPanelType,
|
||||
);
|
||||
@@ -66,14 +70,56 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
|
||||
setFontLayoutSettingsDialogOpen(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const container = tabsRef.current;
|
||||
if (!container) return;
|
||||
|
||||
const checkButtonWidths = () => {
|
||||
const threshold = (container.clientWidth - 64) * 0.3;
|
||||
const hideLabel = Array.from(container.querySelectorAll('button')).some((button) => {
|
||||
const labelSpan = button.querySelector('span');
|
||||
const labelText = labelSpan?.textContent || '';
|
||||
const clone = button.cloneNode(true) as HTMLButtonElement;
|
||||
clone.style.position = 'absolute';
|
||||
clone.style.visibility = 'hidden';
|
||||
clone.style.width = 'auto';
|
||||
const cloneSpan = clone.querySelector('span');
|
||||
if (cloneSpan) {
|
||||
cloneSpan.classList.remove('hidden');
|
||||
cloneSpan.textContent = labelText;
|
||||
}
|
||||
document.body.appendChild(clone);
|
||||
const fullWidth = clone.scrollWidth;
|
||||
document.body.removeChild(clone);
|
||||
return fullWidth > threshold;
|
||||
});
|
||||
setShowTabLabels(!hideLabel);
|
||||
};
|
||||
|
||||
checkButtonWidths();
|
||||
|
||||
const resizeObserver = new ResizeObserver(checkButtonWidths);
|
||||
resizeObserver.observe(container);
|
||||
const mutationObserver = new MutationObserver(checkButtonWidths);
|
||||
mutationObserver.observe(container, {
|
||||
subtree: true,
|
||||
characterData: true,
|
||||
});
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
mutationObserver.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
isOpen={true}
|
||||
onClose={handleClose}
|
||||
className='modal-open'
|
||||
boxClassName='sm:min-w-[520px]'
|
||||
snapHeight={window.innerWidth < 640 ? 0.7 : undefined}
|
||||
boxClassName={clsx('sm:min-w-[520px]', appService?.isMobile && 'sm:max-w-[90%] sm:w-3/4')}
|
||||
snapHeight={appService?.isMobile ? 0.7 : undefined}
|
||||
header={
|
||||
<div className='flex w-full items-center justify-between'>
|
||||
<button
|
||||
@@ -85,10 +131,17 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
|
||||
>
|
||||
{isRtl ? <MdArrowForwardIos /> : <MdArrowBackIosNew />}
|
||||
</button>
|
||||
<div className='dialog-tabs flex h-10 max-w-[100%] flex-grow items-center gap-2 pl-4'>
|
||||
<div
|
||||
ref={tabsRef}
|
||||
className={clsx(
|
||||
'dialog-tabs flex h-10 w-full items-center',
|
||||
showTabLabels ? 'gap-2 sm:gap-1' : 'gap-4',
|
||||
)}
|
||||
>
|
||||
{tabConfig.map(({ tab, icon: Icon, label }) => (
|
||||
<button
|
||||
key={tab}
|
||||
data-tab={tab}
|
||||
className={clsx(
|
||||
'btn btn-ghost text-base-content btn-sm',
|
||||
activePanel === tab ? 'btn-active' : '',
|
||||
@@ -96,21 +149,23 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
|
||||
onClick={() => handleSetActivePanel(tab)}
|
||||
>
|
||||
<Icon className='mr-0' />
|
||||
{window.innerWidth >= 500 ? label : ''}
|
||||
<span className={clsx('label', !showTabLabels && 'hidden')}>{label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className='flex h-full items-center justify-end gap-x-2'>
|
||||
<Dropdown
|
||||
className='dropdown-bottom dropdown-end'
|
||||
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
|
||||
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0 flex items-center justify-center'
|
||||
toggleButton={<PiDotsThreeVerticalBold />}
|
||||
>
|
||||
<DialogMenu />
|
||||
</Dropdown>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className={'bg-base-300/65 btn btn-ghost btn-circle hidden h-6 min-h-6 w-6 sm:flex'}
|
||||
className={
|
||||
'bg-base-300/65 btn btn-ghost btn-circle hidden h-6 min-h-6 w-6 p-0 sm:flex'
|
||||
}
|
||||
>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
|
||||
@@ -76,22 +76,19 @@ export const AboutWindow = () => {
|
||||
boxClassName='sm:!w-96 sm:h-auto'
|
||||
>
|
||||
<div className='about-content flex h-full flex-col items-center justify-center'>
|
||||
<div className='flex flex-col items-center px-8'>
|
||||
<div className='mb-3'>
|
||||
<Image src='/icon.png' alt='App Logo' className='h-24 w-24' width={64} height={64} />
|
||||
<div className='flex flex-col items-center gap-2 px-8'>
|
||||
<div className='mb-2 mt-8'>
|
||||
<Image src='/icon.png' alt='App Logo' className='h-20 w-20' width={64} height={64} />
|
||||
</div>
|
||||
<div className='flex select-text flex-col items-center'>
|
||||
<h2 className='text-2xl font-bold'>Readest</h2>
|
||||
<h2 className='mb-2 text-2xl font-bold'>Readest</h2>
|
||||
<p className='text-neutral-content text-center text-sm'>
|
||||
{_('Version {{version}}', { version: packageJson.version })} {`(${browserInfo})`}
|
||||
</p>
|
||||
</div>
|
||||
<div className='h-5'>
|
||||
{appService?.hasUpdater && !updateStatus && (
|
||||
<span
|
||||
className='badge badge-primary mt-2 cursor-pointer p-1'
|
||||
onClick={handleCheckUpdate}
|
||||
>
|
||||
<span className='badge badge-primary cursor-pointer p-1' onClick={handleCheckUpdate}>
|
||||
{_('Check Update')}
|
||||
</span>
|
||||
)}
|
||||
@@ -107,7 +104,7 @@ export const AboutWindow = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='divider py-12 sm:py-2'></div>
|
||||
<div className='divider py-16 sm:py-2'></div>
|
||||
|
||||
<div className='flex flex-col items-center px-4 text-center' dir='ltr'>
|
||||
<p className='text-neutral-content text-sm'>
|
||||
|
||||
@@ -174,22 +174,21 @@ const Dialog: React.FC<DialogProps> = ({
|
||||
appService?.hasSafeAreaInset && isFullHeightInMobile
|
||||
? `max(env(safe-area-inset-top), ${systemUIVisible ? statusBarHeight : 0}px)`
|
||||
: '0px',
|
||||
height: snapHeight ? `${snapHeight * 100}%` : '100%',
|
||||
bottom: 0,
|
||||
...(appService?.isMobile
|
||||
? { height: snapHeight ? `${snapHeight * 100}%` : '100%', bottom: 0 }
|
||||
: {}),
|
||||
}}
|
||||
>
|
||||
{window.innerWidth < 640 && (
|
||||
<div
|
||||
className={clsx(
|
||||
'drag-handle flex h-10 max-h-10 min-h-10 w-full cursor-row-resize items-center justify-center',
|
||||
'transition-padding-top duration-300 ease-out',
|
||||
)}
|
||||
onMouseDown={handleDragStart}
|
||||
onTouchStart={handleDragStart}
|
||||
>
|
||||
<div className='bg-base-content/50 h-1 w-10 rounded-full'></div>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={clsx(
|
||||
'drag-handle h-10 max-h-10 min-h-10 w-full cursor-row-resize items-center justify-center',
|
||||
'transition-padding-top flex duration-300 ease-out sm:hidden',
|
||||
)}
|
||||
onMouseDown={handleDragStart}
|
||||
onTouchStart={handleDragStart}
|
||||
>
|
||||
<div className='bg-base-content/50 h-1 w-10 rounded-full'></div>
|
||||
</div>
|
||||
<div className='dialog-header bg-base-100 sticky top-1 z-10 flex items-center justify-between px-4'>
|
||||
{header ? (
|
||||
header
|
||||
|
||||
@@ -27,7 +27,7 @@ export const useTheme = ({
|
||||
|
||||
useEffect(() => {
|
||||
updateAppTheme(appThemeColor);
|
||||
if (appService?.isMobile) {
|
||||
if (appService?.isMobileApp) {
|
||||
if (systemUIVisible) {
|
||||
showSystemUI();
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user