Tom Butcher 78dc567a8f
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
Implemented software update installation.
2026-06-21 15:18:04 +01:00

234 lines
6.2 KiB
JavaScript

import { createContext, useCallback, useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import { useNavigate } from 'react-router-dom'
// Only available in Electron renderer
const electron = window.require ? window.require('electron') : null
const ipcRenderer = electron ? electron.ipcRenderer : null
// Utility to check if running in Electron
// eslint-disable-next-line react-refresh/only-export-components
export function isElectron() {
// Renderer process
if (
typeof window !== 'undefined' &&
window.process &&
window.process.type === 'renderer'
) {
return true
}
// User agent
if (
typeof navigator === 'object' &&
typeof navigator.userAgent === 'string' &&
navigator.userAgent.indexOf('Electron') >= 0
) {
return true
}
return false
}
const ElectronContext = createContext()
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()
// Function to open external URL via Electron
const openExternalUrl = (url) => {
if (electronAvailable && ipcRenderer) {
ipcRenderer.invoke('open-external-url', url)
return true
}
return false
}
// Function to open internal URL via Electron
const openInternalUrl = (url) => {
if (electronAvailable && ipcRenderer) {
ipcRenderer.invoke('open-internal-url', url)
return true
}
return false
}
useEffect(() => {
if (!ipcRenderer) return
// Get initial platform
ipcRenderer.invoke('os-info').then((info) => {
if (info && info.platform) setPlatform(info.platform)
})
// Get initial window state
ipcRenderer.invoke('window-state').then((state) => {
if (state && typeof state.isMaximized === 'boolean') {
setIsMaximized(state.isMaximized)
}
if (state && typeof state.isFullScreen === 'boolean') {
setIsFullScreen(state.isFullScreen)
}
})
// Listen for window state changes
const windowStateHandler = (event, state) => {
if (state && typeof state.isMaximized === 'boolean') {
setIsMaximized(state.isMaximized)
}
if (state && typeof state.isFullScreen === 'boolean') {
setIsFullScreen(state.isFullScreen)
}
}
ipcRenderer.on('window-state', windowStateHandler)
// Listen for navigate
const navigateHandler = (event, url) => {
navigate(url)
}
ipcRenderer.on('navigate', navigateHandler)
return () => {
ipcRenderer.removeListener('navigate', navigateHandler)
ipcRenderer.removeListener('window-state', windowStateHandler)
}
}, [navigate])
// Window control handler
const handleWindowControl = (action) => {
if (electronAvailable && ipcRenderer) {
ipcRenderer.send('window-control', action)
}
}
const getAuthSession = async () => {
if (!electronAvailable || !ipcRenderer) return null
return await ipcRenderer.invoke('auth-session-get')
}
const setAuthSession = async (session) => {
if (!electronAvailable || !ipcRenderer) return false
return await ipcRenderer.invoke('auth-session-set', session)
}
const clearAuthSession = async () => {
if (!electronAvailable || !ipcRenderer) return false
return await ipcRenderer.invoke('auth-session-clear')
}
const getAppSettings = useCallback(async () => {
if (!electronAvailable || !ipcRenderer) return {}
return await ipcRenderer.invoke('app-settings-get')
}, [electronAvailable])
const setAppSettings = useCallback(
async (settings) => {
if (!electronAvailable || !ipcRenderer) return false
return await ipcRenderer.invoke('app-settings-set', settings)
},
[electronAvailable]
)
const startAppUpdate = useCallback(
async (update) => {
if (!electronAvailable || !ipcRenderer) return false
return await ipcRenderer.invoke('app-update-start', update)
},
[electronAvailable]
)
const onAppUpdateProgress = useCallback(
(handler) => {
if (!electronAvailable || !ipcRenderer || typeof handler !== 'function') {
return () => {}
}
const progressHandler = (event, progress) => {
handler(progress)
}
ipcRenderer.on('app-update-progress', progressHandler)
return () => {
ipcRenderer.removeListener('app-update-progress', progressHandler)
}
},
[electronAvailable]
)
// Backwards-compatible helpers
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 || !ipcRenderer) return false
try {
return await ipcRenderer.invoke('spotlight-window-resize', height)
} catch (error) {
console.warn(
'[ElectronContext] Failed to resize spotlight window:',
error
)
return false
}
}
const setSidebarViewMenu = useCallback(
async (sections) => {
if (!electronAvailable || !ipcRenderer) return false
return await ipcRenderer.invoke('set-sidebar-view-menu', sections)
},
[electronAvailable]
)
const getElectronVersion = useCallback(async () => {
if (!electronAvailable || !ipcRenderer) return null
return await ipcRenderer.invoke('electron-version')
}, [electronAvailable])
return (
<ElectronContext.Provider
value={{
platform,
isMaximized,
isFullScreen,
isElectron: electronAvailable,
handleWindowControl,
openExternalUrl,
openInternalUrl,
getAuthSession,
setAuthSession,
clearAuthSession,
getAppSettings,
setAppSettings,
startAppUpdate,
onAppUpdateProgress,
getToken,
setToken,
resizeSpotlightWindow,
setSidebarViewMenu,
getElectronVersion
}}
>
{children}
</ElectronContext.Provider>
)
}
ElectronProvider.propTypes = {
children: PropTypes.node.isRequired
}
export { ElectronContext, ElectronProvider }