feat(vpn-desktop): support screen UI and routing (#4279)
* add base setting screen and routing * add version * add logs icons * fix comments * refactor to nested routes * add settingslayout * remove useless note * fix typecheck * fix exports and optionals * fix icon --------- Co-authored-by: pierre <dommerc.pierre@gmail.com>
This commit is contained in:
@@ -121,3 +121,47 @@ pub async fn set_entry_location_selector(
|
||||
.map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(data_state))]
|
||||
#[tauri::command]
|
||||
pub async fn set_auto_connect(
|
||||
data_state: State<'_, SharedAppData>,
|
||||
entry_selector: bool,
|
||||
) -> Result<(), CmdError> {
|
||||
debug!("set_auto_connect");
|
||||
|
||||
let mut app_data_store = data_state.lock().await;
|
||||
let mut app_data = app_data_store
|
||||
.read()
|
||||
.await
|
||||
.map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?;
|
||||
app_data.autoconnect = Some(entry_selector);
|
||||
app_data_store.data = app_data;
|
||||
app_data_store
|
||||
.write()
|
||||
.await
|
||||
.map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(data_state))]
|
||||
#[tauri::command]
|
||||
pub async fn set_monitoring(
|
||||
data_state: State<'_, SharedAppData>,
|
||||
entry_selector: bool,
|
||||
) -> Result<(), CmdError> {
|
||||
debug!("set_monitoring");
|
||||
|
||||
let mut app_data_store = data_state.lock().await;
|
||||
let mut app_data = app_data_store
|
||||
.read()
|
||||
.await
|
||||
.map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?;
|
||||
app_data.monitoring = Some(entry_selector);
|
||||
app_data_store.data = app_data;
|
||||
app_data_store
|
||||
.write()
|
||||
.await
|
||||
.map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -108,6 +108,8 @@ async fn main() -> Result<()> {
|
||||
app_data::set_app_data,
|
||||
app_data::set_ui_theme,
|
||||
app_data::set_entry_location_selector,
|
||||
app_data::set_monitoring,
|
||||
app_data::set_auto_connect,
|
||||
app_data::get_node_countries,
|
||||
app_data::set_root_font_size,
|
||||
node_location::set_node_location,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "nym-vpn",
|
||||
"version": "0.0.0"
|
||||
"version": "0.0.1"
|
||||
},
|
||||
"tauri": {
|
||||
"updater": {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
export const routes = {
|
||||
root: '/',
|
||||
settings: '/settings',
|
||||
display: '/settings/display',
|
||||
logs: '/settings/logs',
|
||||
feedback: '/settings/feedback',
|
||||
legal: '/settings/legal',
|
||||
entryNodeLocation: '/entry-node-location',
|
||||
exitNodeLocation: '/exit-node-location',
|
||||
} as const;
|
||||
|
||||
@@ -3,5 +3,9 @@
|
||||
"disconnect": "Disconnect",
|
||||
"first-hop-selection": "First hop selection",
|
||||
"last-hop-selection": "Last hop selection",
|
||||
"settings": "Settings"
|
||||
"settings": "Settings",
|
||||
"display-theme": "Display theme",
|
||||
"logs": "Logs",
|
||||
"feedback": "Feedback",
|
||||
"legal": "Legal"
|
||||
}
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
{
|
||||
"entry-selector": "Entry location selector"
|
||||
"auto-connect": {
|
||||
"title": "Auto-connect",
|
||||
"desc": "Auto connect at app startup"
|
||||
},
|
||||
"entry-selector": {
|
||||
"title": "Entry location selector",
|
||||
"desc": "Manually select your entry hop"
|
||||
},
|
||||
"feedback": "Feedback",
|
||||
"error-reporting": {
|
||||
"title": "Error reporting",
|
||||
"desc": "Help us improve NymVPN"
|
||||
},
|
||||
"faq": "FAQ",
|
||||
"display-theme": "Display theme",
|
||||
"logs": "Logs",
|
||||
"legal": "Legal",
|
||||
"quit": "Quit NymVPN"
|
||||
}
|
||||
|
||||
@@ -12,8 +12,14 @@ import ConnectionStatus from './ConnectionStatus';
|
||||
import HopSelect from './HopSelect';
|
||||
|
||||
function Home() {
|
||||
const { state, loading, exitNodeLocation, defaultNodeLocation } =
|
||||
useMainState();
|
||||
const {
|
||||
state,
|
||||
loading,
|
||||
entryNodeLocation,
|
||||
exitNodeLocation,
|
||||
entrySelector,
|
||||
defaultNodeLocation,
|
||||
} = useMainState();
|
||||
const dispatch = useMainDispatch() as StateDispatch;
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation('home');
|
||||
@@ -77,6 +83,17 @@ function Home() {
|
||||
<div className="mt-3 font-semibold text-lg">
|
||||
{t('select-node-title')}
|
||||
</div>
|
||||
{entrySelector && (
|
||||
<HopSelect
|
||||
country={entryNodeLocation || defaultNodeLocation}
|
||||
onClick={() => {
|
||||
if (state === 'Disconnected') {
|
||||
navigate(routes.entryNodeLocation);
|
||||
}
|
||||
}}
|
||||
nodeHop="entry"
|
||||
/>
|
||||
)}
|
||||
<HopSelect
|
||||
country={exitNodeLocation || defaultNodeLocation}
|
||||
onClick={() => {
|
||||
|
||||
@@ -2,61 +2,216 @@ import { useEffect, useState } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { Switch } from '@headlessui/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { routes } from '../../constants';
|
||||
import { useMainDispatch, useMainState } from '../../contexts';
|
||||
import { StateDispatch } from '../../types';
|
||||
import UiScaler from './UiScaler';
|
||||
import SettingsGroup from './SettingsGroup';
|
||||
|
||||
function Settings() {
|
||||
const state = useMainState();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useMainDispatch() as StateDispatch;
|
||||
const { t } = useTranslation('settings');
|
||||
|
||||
const [darkModeEnabled, setDarkModeEnabled] = useState(
|
||||
state.uiTheme === 'Dark',
|
||||
);
|
||||
const [entrySelector, setEntrySelector] = useState(state.entrySelector);
|
||||
const [autoConnect, setAutoConnect] = useState(state.autoConnect);
|
||||
const [monitoring, setMonitoring] = useState(state.monitoring);
|
||||
|
||||
useEffect(() => {
|
||||
setDarkModeEnabled(state.uiTheme === 'Dark');
|
||||
setEntrySelector(state.entrySelector);
|
||||
setAutoConnect(state.autoConnect);
|
||||
setMonitoring(state.monitoring);
|
||||
}, [state]);
|
||||
|
||||
const handleThemeChange = async (darkMode: boolean) => {
|
||||
if (darkMode && state.uiTheme === 'Light') {
|
||||
dispatch({ type: 'set-ui-theme', theme: 'Dark' });
|
||||
} else if (!darkMode && state.uiTheme === 'Dark') {
|
||||
dispatch({ type: 'set-ui-theme', theme: 'Light' });
|
||||
}
|
||||
invoke<void>('set_ui_theme', { theme: darkMode ? 'Dark' : 'Light' }).catch(
|
||||
(e) => {
|
||||
console.log(e);
|
||||
},
|
||||
);
|
||||
const handleEntrySelectorChange = async () => {
|
||||
const isSelected = !state.entrySelector;
|
||||
dispatch({ type: 'set-entry-selector', entrySelector: isSelected });
|
||||
invoke<void>('set_entry_location_selector', {
|
||||
entrySelector: isSelected,
|
||||
}).catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
};
|
||||
|
||||
const handleAutoConnectChanged = async () => {
|
||||
const isSelected = !state.autoConnect;
|
||||
dispatch({ type: 'set-auto-connect', autoConnect: isSelected });
|
||||
invoke<void>('set_auto_connect', { autoConnect: isSelected }).catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
};
|
||||
|
||||
const handleMonitoringChanged = async () => {
|
||||
const isSelected = !state.monitoring;
|
||||
dispatch({ type: 'set-monitoring', monitoring: isSelected });
|
||||
invoke<void>('set_monitoring', { monitoring: isSelected }).catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col p-4 gap-4">
|
||||
<div className="flex flex-row justify-between items-center">
|
||||
<p className="text-lg text-baltic-sea dark:text-mercury-pinkish">
|
||||
Dark Mode
|
||||
</p>
|
||||
<Switch
|
||||
checked={darkModeEnabled}
|
||||
onChange={handleThemeChange}
|
||||
className={clsx([
|
||||
darkModeEnabled
|
||||
? 'bg-melon'
|
||||
: 'bg-mercury-pinkish dark:bg-gun-powder',
|
||||
'relative inline-flex h-6 w-11 items-center rounded-full',
|
||||
])}
|
||||
>
|
||||
<span className="sr-only">Dark mode</span>
|
||||
<span
|
||||
className={clsx([
|
||||
darkModeEnabled ? 'translate-x-6' : 'translate-x-1',
|
||||
'inline-block h-4 w-4 transform rounded-full bg-cement-feet dark:bg-white transition',
|
||||
])}
|
||||
/>
|
||||
</Switch>
|
||||
<div className="h-full flex flex-col p-4 mt-2 gap-8">
|
||||
<SettingsGroup
|
||||
settings={[
|
||||
{
|
||||
title: t('auto-connect.title'),
|
||||
desc: t('auto-connect.desc'),
|
||||
leadingIcon: 'hdr_auto',
|
||||
trailing: (
|
||||
<Switch
|
||||
checked={autoConnect}
|
||||
onChange={handleAutoConnectChanged}
|
||||
className={clsx([
|
||||
autoConnect
|
||||
? 'bg-melon'
|
||||
: 'bg-mercury-pinkish dark:bg-gun-powder',
|
||||
'relative inline-flex h-6 w-11 items-center rounded-full',
|
||||
])}
|
||||
>
|
||||
<span
|
||||
className={clsx([
|
||||
autoConnect ? 'translate-x-6' : 'translate-x-1',
|
||||
'inline-block h-4 w-4 transform rounded-full bg-cement-feet dark:bg-white transition',
|
||||
])}
|
||||
/>
|
||||
</Switch>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('entry-selector.title'),
|
||||
desc: t('entry-selector.desc'),
|
||||
leadingIcon: 'looks_two',
|
||||
trailing: (
|
||||
<Switch
|
||||
checked={entrySelector}
|
||||
onChange={handleEntrySelectorChange}
|
||||
className={clsx([
|
||||
entrySelector
|
||||
? 'bg-melon'
|
||||
: 'bg-mercury-pinkish dark:bg-gun-powder',
|
||||
'relative inline-flex h-6 w-11 items-center rounded-full',
|
||||
])}
|
||||
>
|
||||
<span
|
||||
className={clsx([
|
||||
entrySelector ? 'translate-x-6' : 'translate-x-1',
|
||||
'inline-block h-4 w-4 transform rounded-full bg-cement-feet dark:bg-white transition',
|
||||
])}
|
||||
/>
|
||||
</Switch>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<SettingsGroup
|
||||
settings={[
|
||||
{
|
||||
title: t('display-theme'),
|
||||
leadingIcon: 'contrast',
|
||||
onClick: () => {
|
||||
navigate(routes.display);
|
||||
},
|
||||
trailing: (
|
||||
<div className="font-icon text-2xl cursor-pointer">
|
||||
arrow_right
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<SettingsGroup
|
||||
settings={[
|
||||
{
|
||||
title: t('logs'),
|
||||
leadingIcon: 'sort',
|
||||
onClick: () => {
|
||||
navigate(routes.logs);
|
||||
},
|
||||
trailing: (
|
||||
<div className="font-icon text-2xl cursor-pointer">
|
||||
arrow_right
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<SettingsGroup
|
||||
settings={[
|
||||
{
|
||||
title: t('feedback'),
|
||||
leadingIcon: 'question_answer',
|
||||
onClick: () => {
|
||||
navigate(routes.feedback);
|
||||
},
|
||||
trailing: (
|
||||
<div className="font-icon text-2xl cursor-pointer">
|
||||
arrow_right
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('error-reporting.title'),
|
||||
desc: t('error-reporting.desc'),
|
||||
leadingIcon: 'error',
|
||||
trailing: (
|
||||
<Switch
|
||||
checked={monitoring}
|
||||
onChange={handleMonitoringChanged}
|
||||
className={clsx([
|
||||
monitoring
|
||||
? 'bg-melon'
|
||||
: 'bg-mercury-pinkish dark:bg-gun-powder',
|
||||
'relative inline-flex h-6 w-11 items-center rounded-full',
|
||||
])}
|
||||
>
|
||||
<span
|
||||
className={clsx([
|
||||
monitoring ? 'translate-x-6' : 'translate-x-1',
|
||||
'inline-block h-4 w-4 transform rounded-full bg-cement-feet dark:bg-white transition',
|
||||
])}
|
||||
/>
|
||||
</Switch>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('faq'),
|
||||
leadingIcon: 'help',
|
||||
trailing: (
|
||||
<div className="font-icon text-2xl cursor-pointer">launch</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<SettingsGroup
|
||||
settings={[
|
||||
{
|
||||
title: t('legal'),
|
||||
onClick: () => {
|
||||
navigate(routes.legal);
|
||||
},
|
||||
trailing: (
|
||||
<div className="font-icon text-2xl cursor-pointer">
|
||||
arrow_right
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<SettingsGroup
|
||||
settings={[
|
||||
{
|
||||
title: t('quit'),
|
||||
onClick: () => {
|
||||
//TODO shutdown gracefully
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<div className="text-comet text-sm tracking-tight leading-tight">
|
||||
Version {state.version}
|
||||
</div>
|
||||
<UiScaler />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import clsx from 'clsx';
|
||||
import { ReactNode } from 'react';
|
||||
import { RadioGroup } from '@headlessui/react';
|
||||
|
||||
type Setting = {
|
||||
title: string;
|
||||
leadingIcon?: string;
|
||||
desc?: string;
|
||||
onClick?: () => void;
|
||||
trailing?: ReactNode;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
settings: Setting[];
|
||||
}
|
||||
|
||||
function SettingsGroup({ settings }: Props) {
|
||||
return (
|
||||
<RadioGroup>
|
||||
{settings.map((setting, index) => (
|
||||
<RadioGroup.Option
|
||||
key={setting.title}
|
||||
value={setting.title}
|
||||
onClick={setting.onClick}
|
||||
className={clsx([
|
||||
'bg-white dark:bg-baltic-sea-jaguar relative flex px-5 py-2 shadow-md focus:outline-none',
|
||||
index === 0 && 'rounded-t-lg',
|
||||
index === settings.length - 1 &&
|
||||
settings.length === 2 &&
|
||||
'border-t border-mercury-mist',
|
||||
index !== 0 &&
|
||||
index !== settings.length - 1 &&
|
||||
'border-y border-mercury-mist',
|
||||
index === settings.length - 1 && 'rounded-b-lg',
|
||||
setting.desc === undefined && 'py-4',
|
||||
])}
|
||||
>
|
||||
<div className="flex flex-1 items-center justify-between gap-4">
|
||||
{setting.leadingIcon && (
|
||||
<span className="font-icon text-2xl">{setting.leadingIcon}</span>
|
||||
)}
|
||||
<div className="flex flex-1 items-center">
|
||||
<div className="text-sm">
|
||||
<RadioGroup.Label
|
||||
as="p"
|
||||
className="font-semibold text-lg text-baltic-sea dark:text-mercury-pinkish"
|
||||
>
|
||||
{setting.title}
|
||||
</RadioGroup.Label>
|
||||
<RadioGroup.Description
|
||||
as="span"
|
||||
className="text-base text-cement-feet dark:text-mercury-mist"
|
||||
>
|
||||
<span>{setting.desc}</span>
|
||||
</RadioGroup.Description>
|
||||
</div>
|
||||
</div>
|
||||
<div>{setting.trailing}</div>
|
||||
</div>
|
||||
</RadioGroup.Option>
|
||||
))}
|
||||
</RadioGroup>
|
||||
);
|
||||
}
|
||||
|
||||
export default SettingsGroup;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
function SettingsLayout() {
|
||||
return (
|
||||
<div>
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SettingsLayout;
|
||||
@@ -0,0 +1,64 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { Switch } from '@headlessui/react';
|
||||
import { useMainDispatch, useMainState } from '../../../contexts';
|
||||
import { StateDispatch } from '../../../types';
|
||||
import UiScaler from './UiScaler';
|
||||
|
||||
function Display() {
|
||||
const state = useMainState();
|
||||
const dispatch = useMainDispatch() as StateDispatch;
|
||||
|
||||
const [darkModeEnabled, setDarkModeEnabled] = useState(
|
||||
state.uiTheme === 'Dark',
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setDarkModeEnabled(state.uiTheme === 'Dark');
|
||||
}, [state]);
|
||||
|
||||
const handleThemeChange = async (darkMode: boolean) => {
|
||||
if (darkMode && state.uiTheme === 'Light') {
|
||||
dispatch({ type: 'set-ui-theme', theme: 'Dark' });
|
||||
} else if (!darkMode && state.uiTheme === 'Dark') {
|
||||
dispatch({ type: 'set-ui-theme', theme: 'Light' });
|
||||
}
|
||||
invoke<void>('set_ui_theme', { theme: darkMode ? 'Dark' : 'Light' }).catch(
|
||||
(e) => {
|
||||
console.log(e);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col p-4 gap-4">
|
||||
<div className="flex flex-row justify-between items-center">
|
||||
<p className="text-lg text-baltic-sea dark:text-mercury-pinkish">
|
||||
Dark Mode
|
||||
</p>
|
||||
<Switch
|
||||
checked={darkModeEnabled}
|
||||
onChange={handleThemeChange}
|
||||
className={clsx([
|
||||
darkModeEnabled
|
||||
? 'bg-melon'
|
||||
: 'bg-mercury-pinkish dark:bg-gun-powder',
|
||||
'relative inline-flex h-6 w-11 items-center rounded-full',
|
||||
])}
|
||||
>
|
||||
<span className="sr-only">Dark mode</span>
|
||||
<span
|
||||
className={clsx([
|
||||
darkModeEnabled ? 'translate-x-6' : 'translate-x-1',
|
||||
'inline-block h-4 w-4 transform rounded-full bg-cement-feet dark:bg-white transition',
|
||||
])}
|
||||
/>
|
||||
</Switch>
|
||||
</div>
|
||||
<UiScaler />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Display;
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { ChangeEvent, useEffect, useState } from 'react';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { useMainDispatch, useMainState } from '../../contexts';
|
||||
import { CmdError, StateDispatch } from '../../types';
|
||||
import { useMainDispatch, useMainState } from '../../../contexts';
|
||||
import { CmdError, StateDispatch } from '../../../types';
|
||||
|
||||
function UiScaler() {
|
||||
const [slideValue, setSlideValue] = useState(12);
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Display } from './Display';
|
||||
@@ -0,0 +1,9 @@
|
||||
function Feedback() {
|
||||
return (
|
||||
<div className="h-full flex flex-col p-4 gap-4">
|
||||
<div className="flex flex-row justify-between items-center"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Feedback;
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Feedback } from './Feedback';
|
||||
@@ -1 +1,5 @@
|
||||
export { default as Settings } from './Settings';
|
||||
export { default as SettingsLayout } from './SettingsLayout';
|
||||
export * from './display';
|
||||
export * from './feedback';
|
||||
export * from './legal';
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
function Legal() {
|
||||
return (
|
||||
<div className="h-full flex flex-col p-4 gap-4">
|
||||
<div className="flex flex-row justify-between items-center"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Legal;
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Legal } from './Legal';
|
||||
@@ -1,5 +1,15 @@
|
||||
import { createBrowserRouter } from 'react-router-dom';
|
||||
import { Error, Home, NavLayout, NodeLocation, Settings } from './pages';
|
||||
import {
|
||||
Display,
|
||||
Error,
|
||||
Feedback,
|
||||
Home,
|
||||
Legal,
|
||||
NavLayout,
|
||||
NodeLocation,
|
||||
Settings,
|
||||
SettingsLayout,
|
||||
} from './pages';
|
||||
import { routes } from './constants';
|
||||
|
||||
const router = createBrowserRouter([
|
||||
@@ -14,8 +24,30 @@ const router = createBrowserRouter([
|
||||
},
|
||||
{
|
||||
path: routes.settings,
|
||||
element: <Settings />,
|
||||
element: <SettingsLayout />,
|
||||
errorElement: <Error />,
|
||||
children: [
|
||||
{
|
||||
element: <Settings />,
|
||||
errorElement: <Error />,
|
||||
index: true,
|
||||
},
|
||||
{
|
||||
path: routes.display,
|
||||
element: <Display />,
|
||||
errorElement: <Error />,
|
||||
},
|
||||
{
|
||||
path: routes.feedback,
|
||||
element: <Feedback />,
|
||||
errorElement: <Error />,
|
||||
},
|
||||
{
|
||||
path: routes.legal,
|
||||
element: <Legal />,
|
||||
errorElement: <Error />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: routes.entryNodeLocation,
|
||||
|
||||
@@ -19,9 +19,12 @@ export type StateAction =
|
||||
| { type: 'new-progress-message'; message: ConnectProgressMsg }
|
||||
| { type: 'connect' }
|
||||
| { type: 'disconnect' }
|
||||
| { type: 'set-version'; version: string }
|
||||
| { type: 'set-connected'; startTime: number }
|
||||
| { type: 'set-connection-start-time'; startTime?: number | null }
|
||||
| { type: 'set-disconnected' }
|
||||
| { type: 'set-auto-connect'; autoConnect: boolean }
|
||||
| { type: 'set-monitoring'; monitoring: boolean }
|
||||
| { type: 'reset' }
|
||||
| { type: 'set-ui-theme'; theme: UiTheme }
|
||||
| { type: 'set-countries'; countries: Country[] }
|
||||
@@ -31,12 +34,15 @@ export type StateAction =
|
||||
|
||||
export const initialState: AppState = {
|
||||
state: 'Disconnected',
|
||||
version: null,
|
||||
loading: false,
|
||||
vpnMode: 'TwoHop',
|
||||
entrySelector: false,
|
||||
tunnel: { name: 'nym', id: 'nym' },
|
||||
uiTheme: 'Light',
|
||||
progressMessages: [],
|
||||
autoConnect: false,
|
||||
monitoring: false,
|
||||
entryNodeLocation: null,
|
||||
exitNodeLocation: null,
|
||||
defaultNodeLocation: {
|
||||
@@ -70,6 +76,16 @@ export function reducer(state: AppState, action: StateAction): AppState {
|
||||
...state,
|
||||
entrySelector: action.entrySelector,
|
||||
};
|
||||
case 'set-auto-connect':
|
||||
return {
|
||||
...state,
|
||||
autoConnect: action.autoConnect,
|
||||
};
|
||||
case 'set-monitoring':
|
||||
return {
|
||||
...state,
|
||||
monitoring: action.monitoring,
|
||||
};
|
||||
case 'set-countries':
|
||||
return {
|
||||
...state,
|
||||
@@ -101,6 +117,11 @@ export function reducer(state: AppState, action: StateAction): AppState {
|
||||
case 'disconnect': {
|
||||
return { ...state, state: 'Disconnecting', loading: true };
|
||||
}
|
||||
case 'set-version':
|
||||
return {
|
||||
...state,
|
||||
version: action.version,
|
||||
};
|
||||
case 'set-connected': {
|
||||
console.log(
|
||||
`__REDUCER [set-connected] changing connection state to Connected`,
|
||||
@@ -152,6 +173,7 @@ export function reducer(state: AppState, action: StateAction): AppState {
|
||||
...state,
|
||||
rootFontSize: action.size,
|
||||
};
|
||||
|
||||
case 'reset':
|
||||
return initialState;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useReducer } from 'react';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { getVersion } from '@tauri-apps/api/app';
|
||||
import { MainDispatchContext, MainStateContext } from '../contexts';
|
||||
import {
|
||||
AppDataFromBackend,
|
||||
@@ -41,6 +42,17 @@ export function MainStateProvider({ children }: Props) {
|
||||
return await invoke<Country>('get_default_node_location');
|
||||
};
|
||||
|
||||
getVersion()
|
||||
.then((version) =>
|
||||
dispatch({
|
||||
type: 'set-version',
|
||||
version,
|
||||
}),
|
||||
)
|
||||
.catch((e) => {
|
||||
console.warn(`command [set-version] returned an error: ${e}`);
|
||||
});
|
||||
|
||||
getInitialConnectionState()
|
||||
.then((state) => dispatch({ type: 'change-connection-state', state }))
|
||||
.catch((e: CmdError) => {
|
||||
@@ -99,6 +111,8 @@ export function MainStateProvider({ children }: Props) {
|
||||
entrySelector: data.entry_location_selector || false,
|
||||
uiTheme: data.ui_theme || 'Light',
|
||||
vpnMode: data.vpn_mode || 'TwoHop',
|
||||
autoConnect: data.autoconnect || false,
|
||||
monitoring: data.monitoring || false,
|
||||
rootFontSize: data.ui_root_font_size || DefaultRootFontSize,
|
||||
};
|
||||
if (data.entry_node_location) {
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface TunnelConfig {
|
||||
|
||||
export type AppState = {
|
||||
state: ConnectionState;
|
||||
version: string | null;
|
||||
loading: boolean;
|
||||
error?: string | null;
|
||||
progressMessages: ConnectProgressMsg[];
|
||||
@@ -27,6 +28,8 @@ export type AppState = {
|
||||
tunnel: TunnelConfig;
|
||||
uiTheme: 'Light' | 'Dark';
|
||||
entrySelector: boolean;
|
||||
autoConnect: boolean;
|
||||
monitoring: boolean;
|
||||
entryNodeLocation: Country | null;
|
||||
exitNodeLocation: Country | null;
|
||||
defaultNodeLocation: Country;
|
||||
|
||||
@@ -45,6 +45,34 @@ export default function TopBar() {
|
||||
navigate(-1);
|
||||
},
|
||||
},
|
||||
'/settings/display': {
|
||||
title: t('display-theme'),
|
||||
leftIcon: 'arrow_back',
|
||||
handleLeftNav: () => {
|
||||
navigate(-1);
|
||||
},
|
||||
},
|
||||
'/settings/logs': {
|
||||
title: t('logs'),
|
||||
leftIcon: 'arrow_back',
|
||||
handleLeftNav: () => {
|
||||
navigate(-1);
|
||||
},
|
||||
},
|
||||
'/settings/feedback': {
|
||||
title: t('feedback'),
|
||||
leftIcon: 'arrow_back',
|
||||
handleLeftNav: () => {
|
||||
navigate(-1);
|
||||
},
|
||||
},
|
||||
'/settings/legal': {
|
||||
title: t('legal'),
|
||||
leftIcon: 'arrow_back',
|
||||
handleLeftNav: () => {
|
||||
navigate(-1);
|
||||
},
|
||||
},
|
||||
'/entry-node-location': {
|
||||
title: t('first-hop-selection'),
|
||||
leftIcon: 'arrow_back',
|
||||
|
||||
@@ -34,6 +34,7 @@ export default {
|
||||
cornflower: '#7075FF',
|
||||
// [DL] error status text
|
||||
teaberry: '#E33B5A',
|
||||
comet: '#625B71',
|
||||
// [DL] "Connected" status text
|
||||
'vert-menthe': '#2BC761',
|
||||
// [D] main titles text
|
||||
|
||||
Reference in New Issue
Block a user