/* * Copyright (c) 2015 Nordic Semiconductor ASA * * SPDX-License-Identifier: LicenseRef-Nordic-4-Clause */ import 'focus-visible'; import React, { type FC, type ReactNode, useEffect, useMemo, useRef, } from 'react'; import Carousel from 'react-bootstrap/Carousel'; import { createRoot } from 'react-dom/client'; import { useDispatch, useSelector } from 'react-redux'; import { type Reducer } from 'redux'; import { inMain as open } from '../../ipc/open'; import { setNrfutilLogger } from '../../nrfutil/nrfutilLogger'; import About from '../About/About'; import ConfirmCloseDialog from '../ConfirmBeforeClose/ConfirmCloseDialog'; import BrokenDeviceDialog from '../Device/BrokenDeviceDialog/BrokenDeviceDialog'; import { setAutoReselect } from '../Device/deviceAutoSelectSlice'; import { getDevices, selectedDevice as selectedDeviceSelector, selectedSerialNumber as selectedSerialNumberSelector, } from '../Device/deviceSlice'; import ErrorBoundary from '../ErrorBoundary/ErrorBoundary'; import ErrorDialog from '../ErrorDialog/ErrorDialog'; import FlashMessages from '../FlashMessage/FlashMessage'; import LogViewer from '../Log/LogViewer'; import logger from '../logging'; import NavBar from '../NavBar/NavBar'; import telemetry from '../telemetry/telemetry'; import classNames from '../utils/classNames'; import { getPersistedCurrentPane, getPersistedLogVisible, } from '../utils/persistentStore'; import useHotKey from '../utils/useHotKey'; import { AboutPaneName, currentPane as currentPaneSelector, isLogVisible as isLogVisibleSelector, isSidePanelVisible as isSidePanelVisibleSelector, setCurrentPane, setLogVisible, setPanes, } from './appLayout'; import ConnectedToStore from './ConnectedToStore'; import VisibilityBar from './VisibilityBar'; import './app.scss'; import './shared.scss'; import './tailwind.css'; export interface PaneProps { active: boolean; } export interface Pane { name: string; preHidden?: boolean; preDisabled?: boolean; Main: FC; SidePanel?: FC; } interface ConnectedAppProps { deviceSelect?: ReactNode; panes: Pane[]; sidePanel?: ReactNode; showLogByDefault?: boolean; documentation?: ReactNode[]; feedbackCategories?: string[]; children?: ReactNode; autoReselectByDefault?: boolean; } const ConnectedApp: FC = ({ deviceSelect, panes, sidePanel, showLogByDefault = true, documentation, feedbackCategories, children, autoReselectByDefault = false, }) => { const initApp = useRef(false); if (!initApp.current) { logger.initialise(); setNrfutilLogger(logger); telemetry.setLogger(logger); initApp.current = true; } usePersistedPane(); const isLogVisible = useSelector(isLogVisibleSelector); const currentPane = useSelector(currentPaneSelector); const allPanes = useAllPanes(panes, documentation, feedbackCategories); const currentPaneIndex = allPanes.findIndex(p => p.name === currentPane); const dispatch = useDispatch(); useHotKey({ hotKey: 'alt+l', title: 'Open launcher', isGlobal: true, action: open.openLauncher, }); useEffect(() => { if (currentPane) { telemetry.sendPageView(currentPane); } }, [currentPane]); useEffect(() => { dispatch(setAutoReselect(autoReselectByDefault)); }, [dispatch, autoReselectByDefault]); useEffect(() => { const persistedLogVisible = getPersistedLogVisible(); // If they are equal, the current setting is already correct // getPersistedLogVisible is used to initialise the redux state if (showLogByDefault !== getPersistedLogVisible()) { if (persistedLogVisible !== undefined) { dispatch(setLogVisible(persistedLogVisible)); } else { dispatch(setLogVisible(showLogByDefault)); } } }, [dispatch, showLogByDefault]); const SidePanelComponent = currentPaneIndex >= 0 ? allPanes[currentPaneIndex].SidePanel : null; const currentSidePanel = SidePanelComponent != null ? : sidePanel; const isSidePanelVisible = useSelector(isSidePanelVisibleSelector) && currentSidePanel; return (
{currentSidePanel}
{allPanes.map(({ name, Main }) => (
))}
{children}
); }; const ConnectedErrorBoundary: React.FC = ({ children }) => { const devices = useSelector(getDevices); const selectedDevice = useSelector(selectedDeviceSelector); const selectedSerialNumber = useSelector(selectedSerialNumberSelector); return ( {children} ); }; export default ({ appReducer, ...props }: { appReducer?: Reducer } & ConnectedAppProps) => ( ); const usePersistedPane = () => { const dispatch = useDispatch(); useEffect(() => { const pane = getPersistedCurrentPane(); if (pane) { dispatch(setCurrentPane(pane)); } }, [dispatch]); }; const useAllPanes = ( panes: Pane[], documentation: ReactNode[] | undefined, feedbackCategories?: string[], ) => { const dispatch = useDispatch(); const allPanes = useMemo(() => { const newPanes = [...panes]; newPanes.push({ name: AboutPaneName, Main: props => ( ), }); return newPanes; }, [panes, documentation, feedbackCategories]); useEffect(() => { dispatch(setPanes(allPanes)); }, [dispatch, allPanes]); return allPanes; }; export const render = (App: React.ReactElement) => { const container = document.getElementById('webapp'); if (container == null) { throw new Error('Unable to find root element
'); } createRoot(container).render(App); };