feat(vpn-desktop-ui): add theme switch into settings (#4217)
This commit is contained in:
@@ -3,7 +3,7 @@ use tracing::{debug, instrument};
|
||||
|
||||
use crate::{
|
||||
error::{CmdError, CmdErrorSource},
|
||||
fs::data::AppData,
|
||||
fs::data::{AppData, UiTheme},
|
||||
states::SharedAppData,
|
||||
};
|
||||
|
||||
@@ -44,3 +44,26 @@ pub async fn get_app_data(
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
#[tauri::command]
|
||||
pub async fn set_ui_theme(
|
||||
data_state: State<'_, SharedAppData>,
|
||||
theme: UiTheme,
|
||||
) -> Result<(), CmdError> {
|
||||
debug!("set_ui_theme");
|
||||
|
||||
// save the selected UI theme to disk
|
||||
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.ui_theme = Some(theme);
|
||||
app_data_store.data = app_data;
|
||||
app_data_store
|
||||
.write()
|
||||
.await
|
||||
.map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ fn main() -> Result<()> {
|
||||
connection::get_connection_start_time,
|
||||
app_data::get_app_data,
|
||||
app_data::set_app_data,
|
||||
app_data::set_ui_theme,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function Settings() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return <div>{t('settings')}</div>;
|
||||
}
|
||||
|
||||
export default Settings;
|
||||
@@ -1,5 +1,5 @@
|
||||
export * from './home';
|
||||
export * from './settings';
|
||||
export { default as NavLayout } from './NavLayout';
|
||||
export { default as Settings } from './Settings';
|
||||
export { default as NodeLocation } from './NodeLocation';
|
||||
export { default as Error } from './Error';
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
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';
|
||||
|
||||
function Settings() {
|
||||
const state = useMainState();
|
||||
const dispatch = useMainDispatch() as StateDispatch;
|
||||
|
||||
const [enabled, setEnabled] = useState(state.uiTheme === 'Dark');
|
||||
|
||||
useEffect(() => {
|
||||
setEnabled(state.uiTheme === 'Dark');
|
||||
}, [state.uiTheme]);
|
||||
|
||||
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">
|
||||
<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={enabled}
|
||||
onChange={handleThemeChange}
|
||||
className={clsx([
|
||||
enabled ? '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([
|
||||
enabled ? 'translate-x-6' : 'translate-x-1',
|
||||
'inline-block h-4 w-4 transform rounded-full bg-ciment-feet dark:bg-white transition',
|
||||
])}
|
||||
/>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Settings;
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Settings } from './Settings';
|
||||
@@ -1,5 +1,5 @@
|
||||
import dayjs from 'dayjs';
|
||||
import { AppData, AppState, ConnectionState, VpnMode } from '../types';
|
||||
import { AppData, AppState, ConnectionState, UiTheme, VpnMode } from '../types';
|
||||
|
||||
export type StateAction =
|
||||
| { type: 'set-partial-state'; partialState: Partial<AppState> }
|
||||
@@ -14,7 +14,8 @@ export type StateAction =
|
||||
| { type: 'set-connection-start-time'; startTime?: number | null }
|
||||
| { type: 'set-disconnected' }
|
||||
| { type: 'reset' }
|
||||
| { type: 'set-app-data'; data: AppData };
|
||||
| { type: 'set-app-data'; data: AppData }
|
||||
| { type: 'set-ui-theme'; theme: UiTheme };
|
||||
|
||||
export const initialState: AppState = {
|
||||
state: 'Disconnected',
|
||||
@@ -98,6 +99,12 @@ export function reducer(state: AppState, action: StateAction): AppState {
|
||||
...state,
|
||||
progressMessages: [...state.progressMessages, action.message],
|
||||
};
|
||||
case 'set-ui-theme':
|
||||
return {
|
||||
...state,
|
||||
uiTheme: action.theme,
|
||||
localAppData: { ...state.localAppData, uiTheme: action.theme },
|
||||
};
|
||||
case 'reset':
|
||||
return initialState;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user