diff --git a/src/i18n/locales/en/messages.json b/src/i18n/locales/en/messages.json index 7a7e6f2ce..c42a0c9c4 100644 --- a/src/i18n/locales/en/messages.json +++ b/src/i18n/locales/en/messages.json @@ -207,7 +207,9 @@ "SettingsView": { "Settings": "Settings", "LanguageSelector": "Language selector", - "CurrentLanguage": "Current language" + "CurrentLanguage": "Current language", + "DeveloperMode": "Developer Mode", + "DeveloperOptions": "Developer Options" }, "SupportView": { "Support": "Support", diff --git a/src/ui/components/DeviceOptionsForm/index.tsx b/src/ui/components/DeviceOptionsForm/index.tsx index a4280f741..417d6b609 100644 --- a/src/ui/components/DeviceOptionsForm/index.tsx +++ b/src/ui/components/DeviceOptionsForm/index.tsx @@ -20,6 +20,7 @@ import { } from '../../gql/generated/types'; import ShowAlerts from '../ShowAlerts'; import Loader from '../Loader'; +import useDeveloperMode from '../../hooks/useDeveloperMode'; const styles: Record> = { categoryTitle: { @@ -242,29 +243,33 @@ const DeviceOptionsForm: FunctionComponent = ( }); }; + const { isDeveloperModeEnabled } = useDeveloperMode(); + return ( <> - - - } - label={t('DeviceOptionsForm.StandardMode')} - /> - } - label={t('DeviceOptionsForm.ManualMode')} - /> - - + {isDeveloperModeEnabled && ( + + + } + label={t('DeviceOptionsForm.StandardMode')} + /> + } + label={t('DeviceOptionsForm.ManualMode')} + /> + + + )} {deviceOptions.userDefinesMode === UserDefinesMode.Manual && ( diff --git a/src/ui/components/FirmwareVersionForm/index.tsx b/src/ui/components/FirmwareVersionForm/index.tsx index 75e852677..b492f7021 100644 --- a/src/ui/components/FirmwareVersionForm/index.tsx +++ b/src/ui/components/FirmwareVersionForm/index.tsx @@ -29,6 +29,7 @@ import { import { ChooseFolderResponseBody, IpcRequest } from '../../../ipc'; import ApplicationStorage from '../../storage'; import GitRepository from '../../models/GitRepository'; +import useDeveloperMode from '../../hooks/useDeveloperMode'; const styles: Record> = { tabs: { @@ -405,6 +406,8 @@ const FirmwareVersionForm: FunctionComponent = ( const showBetaFpvAlert = localPath?.toLocaleLowerCase()?.indexOf('betafpv') > -1; + const { isDeveloperModeEnabled } = useDeveloperMode(); + return ( <> = ( label={t('FirmwareVersionForm.OfficialReleases')} value={FirmwareSource.GitTag} /> - - - - + {isDeveloperModeEnabled && ( + + )} + {isDeveloperModeEnabled && ( + + )} + {isDeveloperModeEnabled && ( + + )} + {isDeveloperModeEnabled && ( + + )} {firmwareSource === FirmwareSource.GitTag && gitTags !== undefined && ( diff --git a/src/ui/hooks/useDeveloperMode.tsx b/src/ui/hooks/useDeveloperMode.tsx new file mode 100644 index 000000000..d46139caf --- /dev/null +++ b/src/ui/hooks/useDeveloperMode.tsx @@ -0,0 +1,28 @@ +import { useState, useEffect } from 'react'; +import ApplicationStorage from '../storage'; + +export default function useDeveloperMode() { + const [isDeveloperModeEnabled, setDeveloperModeEnabled] = useState(false); + + useEffect(() => { + (async () => { + const storage = new ApplicationStorage(); + setDeveloperModeEnabled( + (await storage.getDeveloperModeEnabled()) ?? false + ); + })(); + }, []); + + const setDeveloperMode = () => { + setDeveloperModeEnabled(() => { + const storage = new ApplicationStorage(); + storage.setDeveloperModeEnabled(!isDeveloperModeEnabled); + return !isDeveloperModeEnabled; + }); + }; + + return { + isDeveloperModeEnabled, + setDeveloperMode, + }; +} diff --git a/src/ui/storage/index.ts b/src/ui/storage/index.ts index 894a428c3..fe7404f80 100644 --- a/src/ui/storage/index.ts +++ b/src/ui/storage/index.ts @@ -58,6 +58,10 @@ export interface IApplicationStorage { setShowSensitiveFieldData(field: string, value: boolean): Promise; getShowSensitiveFieldData(field: string): Promise; + + setDeveloperModeEnabled(value: boolean): Promise; + + getDeveloperModeEnabled(): Promise; } const DEVICE_OPTIONS_BY_TARGET_KEYSPACE = 'device_options'; @@ -68,6 +72,7 @@ const WIFI_SSID_KEY = 'wifi_ssid'; const WIFI_PASSWORD_KEY = 'wifi_password'; const REGULATORY_DOMAIN_900_KEY = 'regulatory_domain_900'; const REGULATORY_DOMAIN_2400_KEY = 'regulatory_domain_2400'; +const DEVELOPER_MODE = 'developer_mode'; export default class ApplicationStorage implements IApplicationStorage { async saveDeviceOptions( @@ -217,4 +222,22 @@ export default class ApplicationStorage implements IApplicationStorage { } return null; } + + async setDeveloperModeEnabled(value: boolean): Promise { + localStorage.setItem(DEVELOPER_MODE, JSON.stringify(value)); + } + + async getDeveloperModeEnabled(): Promise { + const value = localStorage.getItem(DEVELOPER_MODE); + + if (value) { + try { + return JSON.parse(value); + } catch (e) { + console.error(`failed to parse state for ${DEVELOPER_MODE}`, e); + return null; + } + } + return null; + } } diff --git a/src/ui/views/SettingsView/index.tsx b/src/ui/views/SettingsView/index.tsx index 9a0142c45..71953ff70 100644 --- a/src/ui/views/SettingsView/index.tsx +++ b/src/ui/views/SettingsView/index.tsx @@ -1,12 +1,20 @@ -import { Card, CardContent, Divider } from '@mui/material'; +import { + Card, + CardContent, + Checkbox, + Divider, + FormControlLabel, +} from '@mui/material'; import React, { FunctionComponent } from 'react'; import SettingsIcon from '@mui/icons-material/Settings'; import LanguageIcon from '@mui/icons-material/Language'; +import DeveloperModeIcon from '@mui/icons-material/DeveloperMode'; import { useTranslation } from 'react-i18next'; import CardTitle from '../../components/CardTitle'; import MainLayout from '../../layouts/MainLayout'; import Omnibox, { Option } from '../../components/Omnibox'; import locales from '../../../i18n/locales.json'; +import useDeveloperMode from '../../hooks/useDeveloperMode'; const SettingsView: FunctionComponent = () => { const { t, i18n } = useTranslation(); @@ -28,6 +36,9 @@ const SettingsView: FunctionComponent = () => { ?.label ?? '', }; } + + const { isDeveloperModeEnabled, setDeveloperMode } = useDeveloperMode(); + return ( @@ -45,6 +56,24 @@ const SettingsView: FunctionComponent = () => { options={languages} /> + + } + title={t('SettingsView.DeveloperOptions')} + /> + + { + setDeveloperMode(); + }} + /> + } + label={t('SettingsView.DeveloperMode')} + /> + );