config: add an option to disable double click, closes #1713 (#1715)

This commit is contained in:
Huang Xin
2025-07-30 17:35:07 +08:00
committed by GitHub
parent 47c6d6d058
commit 8fb1745dbb
25 changed files with 77 additions and 33 deletions
@@ -476,5 +476,6 @@
"Failed to create checkout session": "فشل في إنشاء جلسة الدفع",
"Storage": "التخزين",
"Terms of Service": "شروط الخدمة",
"Privacy Policy": "سياسة الخصوصية"
"Privacy Policy": "سياسة الخصوصية",
"Disable Double Click": "تعطيل النقر المزدوج"
}
@@ -460,5 +460,6 @@
"Failed to create checkout session": "Fehler beim Erstellen der Checkout-Sitzung",
"Storage": "Speicher",
"Terms of Service": "Allgemeine Geschäftsbedingungen",
"Privacy Policy": "Datenschutzbestimmungen"
"Privacy Policy": "Datenschutzbestimmungen",
"Disable Double Click": "Doppelklick deaktivieren"
}
@@ -460,5 +460,6 @@
"Failed to create checkout session": "Αποτυχία δημιουργίας συνεδρίας πληρωμής",
"Storage": "Αποθήκευση",
"Terms of Service": "Όροι χρήσης",
"Privacy Policy": "Πολιτική απορρήτου"
"Privacy Policy": "Πολιτική απορρήτου",
"Disable Double Click": "Απενεργοποίηση διπλού κλικ"
}
@@ -464,5 +464,6 @@
"Failed to create checkout session": "Error al crear la sesión de pago",
"Storage": "Almacenamiento",
"Terms of Service": "Condiciones del servicio",
"Privacy Policy": "Política de privacidad"
"Privacy Policy": "Política de privacidad",
"Disable Double Click": "Deshabilitar Doble Clic"
}
@@ -464,5 +464,6 @@
"Failed to create checkout session": "Échec de la création de la session de paiement",
"Storage": "Stockage",
"Terms of Service": "Conditions d'utilisation",
"Privacy Policy": "Politique de confidentialité"
"Privacy Policy": "Politique de confidentialité",
"Disable Double Click": "Désactiver le double-clic"
}
@@ -460,5 +460,6 @@
"Failed to create checkout session": "चेकआउट सत्र बनाने में विफल",
"Storage": "भंडारण",
"Terms of Service": "सेवा की शर्तें",
"Privacy Policy": "गोपनीयता नीति"
"Privacy Policy": "गोपनीयता नीति",
"Disable Double Click": "डबल क्लिक अक्षम करें"
}
@@ -456,5 +456,6 @@
"Failed to create checkout session": "Gagal membuat sesi checkout",
"Storage": "Penyimpanan",
"Terms of Service": "Ketentuan Layanan",
"Privacy Policy": "Kebijakan Privasi"
"Privacy Policy": "Kebijakan Privasi",
"Disable Double Click": "Nonaktifkan Klik Ganda"
}
@@ -464,5 +464,6 @@
"Failed to create checkout session": "Impossibile creare la sessione di checkout",
"Storage": "Archiviazione",
"Terms of Service": "Termini di servizio",
"Privacy Policy": "Informativa sulla privacy"
"Privacy Policy": "Informativa sulla privacy",
"Disable Double Click": "Disabilita Doppio Clic"
}
@@ -456,5 +456,6 @@
"Failed to create checkout session": "チェックアウトセッションの作成に失敗しました",
"Storage": "ストレージ",
"Terms of Service": "利用規約",
"Privacy Policy": "プライバシーポリシー"
"Privacy Policy": "プライバシーポリシー",
"Disable Double Click": "ダブルクリック無効"
}
@@ -456,5 +456,6 @@
"Failed to create checkout session": "체크아웃 세션 생성 실패",
"Storage": "저장소",
"Terms of Service": "서비스 약관",
"Privacy Policy": "개인정보 보호정책"
"Privacy Policy": "개인정보 보호정책",
"Disable Double Click": "더블 클릭 비활성화"
}
@@ -460,5 +460,6 @@
"Failed to create checkout session": "Kon de afreken sessie niet aanmaken",
"Storage": "Opslag",
"Terms of Service": "Gebruiksvoorwaarden",
"Privacy Policy": "Privacybeleid"
"Privacy Policy": "Privacybeleid",
"Disable Double Click": "Dubbelklikken uitschakelen"
}
@@ -468,5 +468,6 @@
"Failed to create checkout session": "Nie udało się utworzyć sesji zakupu",
"Storage": "Przechowywanie",
"Terms of Service": "Warunki korzystania z usługi",
"Privacy Policy": "Polityka prywatności"
"Privacy Policy": "Polityka prywatności",
"Disable Double Click": "Wyłącz podwójne kliknięcie"
}
@@ -464,5 +464,6 @@
"Failed to create checkout session": "Falha ao criar sessão de checkout",
"Storage": "Armazenamento",
"Terms of Service": "Termos de Serviço",
"Privacy Policy": "Política de Privacidade"
"Privacy Policy": "Política de Privacidade",
"Disable Double Click": "Desativar Clique Duplo"
}
@@ -468,5 +468,6 @@
"Failed to create checkout session": "Не удалось создать сессию оформления заказа",
"Storage": "Хранилище",
"Terms of Service": "Условия использования",
"Privacy Policy": "Политика конфиденциальности"
"Privacy Policy": "Политика конфиденциальности",
"Disable Double Click": "Отключить двойной клик"
}
@@ -456,5 +456,6 @@
"Failed to create checkout session": "ไม่สามารถสร้างเซสชันการชำระเงินได้",
"Storage": "พื้นที่เก็บข้อมูล",
"Terms of Service": "ข้อกำหนดในการให้บริการ",
"Privacy Policy": "นโยบายความเป็นส่วนตัว"
"Privacy Policy": "นโยบายความเป็นส่วนตัว",
"Disable Double Click": "ปิดการคลิกสองครั้ง"
}
@@ -460,5 +460,6 @@
"Failed to create checkout session": "Ödeme oturumu oluşturma başarısız oldu",
"Storage": "Depolama",
"Terms of Service": "Hizmet Şartları",
"Privacy Policy": "Gizlilik Politikası"
"Privacy Policy": "Gizlilik Politikası",
"Disable Double Click": "Çift Tıklamayı Devre Dışı Bırak"
}
@@ -468,5 +468,6 @@
"Failed to create checkout session": "Не вдалося створити сесію оформлення замовлення",
"Storage": "Сховище",
"Terms of Service": "Умови обслуговування",
"Privacy Policy": "Політика конфіденційності"
"Privacy Policy": "Політика конфіденційності",
"Disable Double Click": "Вимкнути подвійне клацання"
}
@@ -456,5 +456,6 @@
"Failed to create checkout session": "Không thể tạo phiên làm việc thanh toán",
"Storage": "Lưu trữ",
"Terms of Service": "Điều khoản dịch vụ",
"Privacy Policy": "Chính sách bảo mật"
"Privacy Policy": "Chính sách bảo mật",
"Disable Double Click": "Tắt Nhấp Đôi"
}
@@ -456,5 +456,6 @@
"Failed to create checkout session": "创建结账会话失败",
"Storage": "云同步空间",
"Terms of Service": "服务条款",
"Privacy Policy": "隐私政策"
"Privacy Policy": "隐私政策",
"Disable Double Click": "禁用双击"
}
@@ -456,5 +456,6 @@
"Failed to create checkout session": "創建結帳會話失敗",
"Storage": "雲同步空間",
"Terms of Service": "服務條款",
"Privacy Policy": "隱私政策"
"Privacy Policy": "隱私政策",
"Disable Double Click": "禁用雙擊"
}
@@ -66,6 +66,7 @@ const FoliateViewer: React.FC<{
const viewRef = useRef<FoliateView | null>(null);
const containerRef = useRef<HTMLDivElement>(null);
const isViewCreated = useRef(false);
const doubleClickDisabled = useRef(!!viewSettings?.disableDoubleClick);
const [toastMessage, setToastMessage] = useState('');
useEffect(() => {
@@ -159,7 +160,7 @@ const FoliateViewer: React.FC<{
detail.doc.addEventListener('keydown', handleKeydown.bind(null, bookKey));
detail.doc.addEventListener('mousedown', handleMousedown.bind(null, bookKey));
detail.doc.addEventListener('mouseup', handleMouseup.bind(null, bookKey));
detail.doc.addEventListener('click', handleClick.bind(null, bookKey));
detail.doc.addEventListener('click', handleClick.bind(null, bookKey, doubleClickDisabled));
detail.doc.addEventListener('wheel', handleWheel.bind(null, bookKey));
detail.doc.addEventListener('touchstart', handleTouchStart.bind(null, bookKey));
detail.doc.addEventListener('touchmove', handleTouchMove.bind(null, bookKey));
@@ -256,6 +257,7 @@ const FoliateViewer: React.FC<{
view.renderer.setStyles?.(getStyles(viewSettings));
applyTranslationStyle(viewSettings);
doubleClickDisabled.current = viewSettings.disableDoubleClick!;
const animated = viewSettings.animated!;
const maxColumnCount = viewSettings.maxColumnCount!;
const maxInlineSize = getMaxInlineSize(viewSettings);
@@ -325,6 +327,12 @@ const FoliateViewer: React.FC<{
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [themeCode, isDarkMode, viewSettings?.overrideColor, viewSettings?.invertImgColorInDark]);
useEffect(() => {
if (viewRef.current && viewRef.current.renderer) {
doubleClickDisabled.current = !!viewSettings?.disableDoubleClick;
}
}, [viewSettings?.disableDoubleClick]);
useEffect(() => {
if (viewRef.current && viewRef.current.renderer && viewSettings) {
applyMarginAndGap();
@@ -27,6 +27,9 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
const [volumeKeysToFlip, setVolumeKeysToFlip] = useState(viewSettings.volumeKeysToFlip!);
const [isDisableClick, setIsDisableClick] = useState(viewSettings.disableClick!);
const [swapClickArea, setSwapClickArea] = useState(viewSettings.swapClickArea!);
const [isDisableDoubleClick, setIsDisableDoubleClick] = useState(
viewSettings.disableDoubleClick!,
);
const [animated, setAnimated] = useState(viewSettings.animated!);
const [allowScript, setAllowScript] = useState(viewSettings.allowScript!);
@@ -90,6 +93,11 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isDisableClick]);
useEffect(() => {
saveViewSettings(envConfig, bookKey, 'disableDoubleClick', isDisableDoubleClick, false, false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isDisableDoubleClick]);
useEffect(() => {
saveViewSettings(envConfig, bookKey, 'swapClickArea', swapClickArea, false, false);
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -172,6 +180,15 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
onChange={() => setSwapClickArea(!swapClickArea)}
/>
</div>
<div className='config-item'>
<span className=''>{_('Disable Double Click')}</span>
<input
type='checkbox'
className='toggle'
checked={isDisableDoubleClick}
onChange={() => setIsDisableDoubleClick(!isDisableDoubleClick)}
/>
</div>
{appService?.isMobileApp && (
<div className='config-item'>
<span className=''>{_('Volume Keys for Page Flip')}</span>
@@ -1,13 +1,5 @@
import {
DISABLE_DOUBLE_CLICK_ON_MOBILE,
DOUBLE_CLICK_INTERVAL_THRESHOLD_MS,
LONG_HOLD_THRESHOLD,
} from '@/services/constants';
import { DOUBLE_CLICK_INTERVAL_THRESHOLD_MS, LONG_HOLD_THRESHOLD } from '@/services/constants';
import { eventDispatcher } from '@/utils/event';
import { getOSPlatform } from '@/utils/misc';
const doubleClickEnabled =
!DISABLE_DOUBLE_CLICK_ON_MOBILE || !['android', 'ios'].includes(getOSPlatform());
let lastClickTime = 0;
let longHoldTimeout: ReturnType<typeof setTimeout> | null = null;
@@ -93,10 +85,14 @@ export const handleWheel = (bookKey: string, event: WheelEvent) => {
);
};
export const handleClick = (bookKey: string, event: MouseEvent) => {
export const handleClick = (
bookKey: string,
doubleClickDisabled: React.MutableRefObject<boolean>,
event: MouseEvent,
) => {
const now = Date.now();
if (doubleClickEnabled && now - lastClickTime < DOUBLE_CLICK_INTERVAL_THRESHOLD_MS) {
if (!doubleClickDisabled.current && now - lastClickTime < DOUBLE_CLICK_INTERVAL_THRESHOLD_MS) {
lastClickTime = now;
window.postMessage(
{
@@ -156,7 +152,7 @@ export const handleClick = (bookKey: string, event: MouseEvent) => {
'*',
);
};
if (doubleClickEnabled) {
if (!doubleClickDisabled.current) {
setTimeout(() => {
if (Date.now() - lastClickTime >= DOUBLE_CLICK_INTERVAL_THRESHOLD_MS) {
postSingleClick();
@@ -105,6 +105,7 @@ export const DEFAULT_BOOK_LAYOUT: BookLayout = {
scrolled: false,
disableClick: false,
swapClickArea: false,
disableDoubleClick: false,
volumeKeysToFlip: false,
continuousScroll: false,
maxColumnCount: 2,
@@ -143,6 +144,7 @@ export const DEFAULT_MOBILE_VIEW_SETTINGS: Partial<ViewSettings> = {
animated: true,
defaultFont: 'Sans-serif',
marginBottomPx: 16,
disableDoubleClick: true,
};
export const DEFAULT_CJK_VIEW_SETTINGS: Partial<ViewSettings> = {
+1
View File
@@ -92,6 +92,7 @@ export interface BookLayout {
scrolled: boolean;
disableClick: boolean;
swapClickArea: boolean;
disableDoubleClick: boolean;
volumeKeysToFlip: boolean;
continuousScroll: boolean;
maxColumnCount: number;