All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Simplified the `isElectron` function to directly return the result of `isElectrobunBridgeReady` or `isElectrobunDesktop`. - Removed redundant checks and consolidated URL opening logic to enhance clarity and reduce code duplication. - Updated event handling for Electron-specific features to utilize the desktopBridge API, improving maintainability and performance.
331 lines
9.1 KiB
JavaScript
331 lines
9.1 KiB
JavaScript
import { createContext, useCallback, useEffect, useRef, useState } from 'react'
|
|
import PropTypes from 'prop-types'
|
|
import { useNavigate } from 'react-router-dom'
|
|
import desktopBridge, {
|
|
isElectrobunBridgeReady,
|
|
isElectrobunDesktop
|
|
} from '../../../electrobun-bridge.js'
|
|
|
|
// eslint-disable-next-line react-refresh/only-export-components
|
|
export function isElectron() {
|
|
return isElectrobunBridgeReady() || isElectrobunDesktop()
|
|
}
|
|
|
|
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]
|
|
)
|
|
|
|
const applyWindowState = useCallback((state) => {
|
|
if (state && typeof state.isMaximized === 'boolean') {
|
|
setIsMaximized(state.isMaximized)
|
|
}
|
|
if (state && typeof state.isFullScreen === 'boolean') {
|
|
setIsFullScreen(state.isFullScreen)
|
|
}
|
|
}, [])
|
|
|
|
const openExternalUrl = (url) => {
|
|
if (!electronAvailable) return false
|
|
void desktopBridge.openExternalUrl(url).catch((error) => {
|
|
console.warn('[ElectronContext] Failed to open external url:', error)
|
|
})
|
|
return true
|
|
}
|
|
|
|
const openInternalUrl = (url) => {
|
|
if (!electronAvailable) return false
|
|
void desktopBridge.openInternalUrl(url).catch((error) => {
|
|
console.warn('[ElectronContext] Failed to open internal url:', error)
|
|
})
|
|
return true
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (!electronAvailable) return
|
|
|
|
document.body.classList.add('electron-body')
|
|
return () => {
|
|
document.body.classList.remove('electron-body')
|
|
}
|
|
}, [electronAvailable])
|
|
|
|
useEffect(() => {
|
|
if (!electronAvailable) return
|
|
|
|
desktopBridge.getOsInfo().then((info) => {
|
|
if (info?.platform) {
|
|
setPlatform(info.platform)
|
|
if (info.platform === 'darwin') {
|
|
document.documentElement.classList.add('macos-vibrancy')
|
|
}
|
|
}
|
|
})
|
|
|
|
desktopBridge.getWindowState().then(applyWindowState)
|
|
|
|
const unsubWindowState = desktopBridge.onMessage(
|
|
'windowState',
|
|
applyWindowState
|
|
)
|
|
const unsubNavigate = desktopBridge.onMessage('navigate', (url) => {
|
|
if (url.toLowerCase() == '/favicon.ico') {
|
|
return
|
|
}
|
|
console.log('[ElectronContext] Navigating to:', url)
|
|
navigate(url)
|
|
})
|
|
const unsubNavigationGesture = desktopBridge.onMessage(
|
|
'navigationGesture',
|
|
navigateHistory
|
|
)
|
|
|
|
return () => {
|
|
unsubWindowState()
|
|
unsubNavigate()
|
|
unsubNavigationGesture()
|
|
}
|
|
}, [applyWindowState, electronAvailable, 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])
|
|
|
|
const handleWindowControl = (action) => {
|
|
if (!electronAvailable) return
|
|
void desktopBridge.windowControl(action)
|
|
}
|
|
|
|
const getAuthSession = async () => {
|
|
if (!electronAvailable) return null
|
|
return await desktopBridge.getAuthSession()
|
|
}
|
|
|
|
const setAuthSession = async (session) => {
|
|
if (!electronAvailable) return false
|
|
const result = await desktopBridge.setAuthSession(session)
|
|
return result?.ok ?? false
|
|
}
|
|
|
|
const clearAuthSession = async () => {
|
|
if (!electronAvailable) return false
|
|
const result = await desktopBridge.clearAuthSession()
|
|
return result?.ok ?? false
|
|
}
|
|
|
|
const getAppSettings = useCallback(async () => {
|
|
if (!electronAvailable) return {}
|
|
return await desktopBridge.getAppSettings()
|
|
}, [electronAvailable])
|
|
|
|
const setAppSettings = useCallback(
|
|
async (settings) => {
|
|
if (!electronAvailable) return false
|
|
const result = await desktopBridge.setAppSettings(settings)
|
|
return result?.ok ?? false
|
|
},
|
|
[electronAvailable]
|
|
)
|
|
|
|
const startAppUpdate = useCallback(
|
|
async (update) => {
|
|
if (!electronAvailable) return false
|
|
const result = await desktopBridge.startAppUpdate(update)
|
|
return result?.ok ?? false
|
|
},
|
|
[electronAvailable]
|
|
)
|
|
|
|
const checkAppUpdateResult = useCallback(async () => {
|
|
if (!electronAvailable) return null
|
|
return await desktopBridge.checkAppUpdateResult()
|
|
}, [electronAvailable])
|
|
|
|
const checkDuplicateInstallations = useCallback(async () => {
|
|
if (!electronAvailable) return null
|
|
return await desktopBridge.checkDuplicateInstallations()
|
|
}, [electronAvailable])
|
|
|
|
const removeDuplicateInstallations = useCallback(async () => {
|
|
if (!electronAvailable) return false
|
|
const result = await desktopBridge.removeDuplicateInstallations()
|
|
return result?.ok ?? false
|
|
}, [electronAvailable])
|
|
|
|
const onDuplicateInstallationsRemoved = useCallback(
|
|
(handler) => {
|
|
if (!electronAvailable || typeof handler !== 'function') {
|
|
return () => {}
|
|
}
|
|
return desktopBridge.onMessage('duplicateInstallationsRemoved', handler)
|
|
},
|
|
[electronAvailable]
|
|
)
|
|
|
|
const onAppUpdateProgress = useCallback(
|
|
(handler) => {
|
|
if (!electronAvailable || typeof handler !== 'function') {
|
|
return () => {}
|
|
}
|
|
return desktopBridge.onMessage('appUpdateProgress', handler)
|
|
},
|
|
[electronAvailable]
|
|
)
|
|
|
|
const onCheckForUpdatesRequest = useCallback(
|
|
(handler) => {
|
|
if (!electronAvailable || typeof handler !== 'function') {
|
|
return () => {}
|
|
}
|
|
return desktopBridge.onMessage('checkForUpdates', handler)
|
|
},
|
|
[electronAvailable]
|
|
)
|
|
|
|
const getToken = async () => {
|
|
const session = await getAuthSession()
|
|
return session?.token || null
|
|
}
|
|
|
|
const setToken = async (token) => {
|
|
const session = (await getAuthSession()) || {}
|
|
return await setAuthSession({ ...session, token })
|
|
}
|
|
|
|
const resizeSpotlightWindow = async (height) => {
|
|
if (!electronAvailable) return false
|
|
try {
|
|
const result = await desktopBridge.resizeSpotlightWindow(height)
|
|
return result?.ok ?? false
|
|
} catch (error) {
|
|
console.warn(
|
|
'[ElectronContext] Failed to resize spotlight window:',
|
|
error
|
|
)
|
|
return false
|
|
}
|
|
}
|
|
|
|
const setSidebarViewMenu = useCallback(
|
|
async (sections) => {
|
|
if (!electronAvailable) return false
|
|
const result = await desktopBridge.setSidebarViewMenu(sections)
|
|
return result?.ok ?? false
|
|
},
|
|
[electronAvailable]
|
|
)
|
|
|
|
const getElectronVersion = useCallback(async () => {
|
|
if (!electronAvailable) return null
|
|
return await desktopBridge.getAppVersion()
|
|
}, [electronAvailable])
|
|
|
|
const getAppEngine = useCallback(async () => {
|
|
if (!electronAvailable) return 'native'
|
|
const engine = await desktopBridge.getAppEngine()
|
|
return engine === 'chromium' ? 'chromium' : 'native'
|
|
}, [electronAvailable])
|
|
|
|
return (
|
|
<ElectronContext.Provider
|
|
value={{
|
|
platform,
|
|
isMaximized,
|
|
isFullScreen,
|
|
isElectron: electronAvailable,
|
|
handleWindowControl,
|
|
openExternalUrl,
|
|
openInternalUrl,
|
|
getAuthSession,
|
|
setAuthSession,
|
|
clearAuthSession,
|
|
getAppSettings,
|
|
setAppSettings,
|
|
startAppUpdate,
|
|
checkAppUpdateResult,
|
|
checkDuplicateInstallations,
|
|
removeDuplicateInstallations,
|
|
onDuplicateInstallationsRemoved,
|
|
onAppUpdateProgress,
|
|
onCheckForUpdatesRequest,
|
|
getToken,
|
|
setToken,
|
|
resizeSpotlightWindow,
|
|
setSidebarViewMenu,
|
|
getElectronVersion,
|
|
getAppEngine
|
|
}}
|
|
>
|
|
{children}
|
|
</ElectronContext.Provider>
|
|
)
|
|
}
|
|
|
|
ElectronProvider.propTypes = {
|
|
children: PropTypes.node.isRequired
|
|
}
|
|
|
|
export { ElectronContext, ElectronProvider }
|