diff --git a/public/mainWindow.js b/public/mainWindow.js index 2b1b59e..0dd67d2 100644 --- a/public/mainWindow.js +++ b/public/mainWindow.js @@ -149,6 +149,33 @@ function attachKeyboardShortcuts(browserWindow) { // Keyboard shortcuts for the main window can be added here if needed } +function sendNavigationGesture(browserWindow, direction) { + if (!browserWindow || browserWindow.isDestroyed()) return + browserWindow.webContents.send('navigation-gesture', direction) +} + +function setupNavigationGestures(browserWindow) { + if (!browserWindow) return + + if (process.platform === 'darwin') { + browserWindow.on('swipe', (_event, direction) => { + if (direction === 'left') { + sendNavigationGesture(browserWindow, 'back') + } else if (direction === 'right') { + sendNavigationGesture(browserWindow, 'forward') + } + }) + } + + browserWindow.on('app-command', (_event, command) => { + if (command === 'browser-backward') { + sendNavigationGesture(browserWindow, 'back') + } else if (command === 'browser-forward') { + sendNavigationGesture(browserWindow, 'forward') + } + }) +} + function setupWindowEvents() { if (!win) return win.on('maximize', () => { @@ -199,6 +226,7 @@ export function createWindow() { setupWindowEvents() attachKeyboardShortcuts(win) + setupNavigationGestures(win) } export function getWindow() { diff --git a/src/components/Dashboard/context/ElectronContext.jsx b/src/components/Dashboard/context/ElectronContext.jsx index 7ef3132..6f0299a 100644 --- a/src/components/Dashboard/context/ElectronContext.jsx +++ b/src/components/Dashboard/context/ElectronContext.jsx @@ -1,4 +1,4 @@ -import { createContext, useCallback, useEffect, useState } from 'react' +import { createContext, useCallback, useEffect, useRef, useState } from 'react' import PropTypes from 'prop-types' import { useNavigate } from 'react-router-dom' @@ -32,12 +32,43 @@ export function isElectron() { const ElectronContext = createContext() +function isInHorizontalScrollContainer(element) { + let node = element + while (node && node !== document.body) { + const { overflowX } = window.getComputedStyle(node) + if ( + (overflowX === 'auto' || overflowX === 'scroll') && + node.scrollWidth > node.clientWidth + ) { + return true + } + node = node.parentElement + } + return false +} + const ElectronProvider = ({ children }) => { const [platform, setPlatform] = useState('unknown') const [isMaximized, setIsMaximized] = useState(false) const [isFullScreen, setIsFullScreen] = useState(false) const [electronAvailable] = useState(isElectron()) const navigate = useNavigate() + const lastNavigationAtRef = useRef(0) + + const navigateHistory = useCallback( + (direction) => { + const now = Date.now() + if (now - lastNavigationAtRef.current < 300) return + lastNavigationAtRef.current = now + + if (direction === 'back') { + navigate(-1) + } else if (direction === 'forward') { + navigate(1) + } + }, + [navigate] + ) // Function to open external URL via Electron const openExternalUrl = (url) => { @@ -92,11 +123,47 @@ const ElectronProvider = ({ children }) => { } ipcRenderer.on('navigate', navigateHandler) + const navigationGestureHandler = (_event, direction) => { + navigateHistory(direction) + } + ipcRenderer.on('navigation-gesture', navigationGestureHandler) + return () => { ipcRenderer.removeListener('navigate', navigateHandler) + ipcRenderer.removeListener('navigation-gesture', navigationGestureHandler) ipcRenderer.removeListener('window-state', windowStateHandler) } - }, [navigate]) + }, [navigate, navigateHistory]) + + useEffect(() => { + if (!electronAvailable || platform !== 'darwin') return + + let accumulatedDeltaX = 0 + let resetTimer + + const handleWheel = (event) => { + if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return + if (Math.abs(event.deltaX) < Math.abs(event.deltaY)) return + if (Math.abs(event.deltaX) < 2) return + if (isInHorizontalScrollContainer(event.target)) return + + accumulatedDeltaX += event.deltaX + clearTimeout(resetTimer) + resetTimer = setTimeout(() => { + if (Math.abs(accumulatedDeltaX) > 60) { + navigateHistory(accumulatedDeltaX > 0 ? 'forward' : 'back') + } + accumulatedDeltaX = 0 + }, 80) + } + + window.addEventListener('wheel', handleWheel, { passive: true }) + + return () => { + window.removeEventListener('wheel', handleWheel) + clearTimeout(resetTimer) + } + }, [electronAvailable, navigateHistory, platform]) // Window control handler const handleWindowControl = (action) => {