applying app settings

This commit is contained in:
Roland Osborne 2024-02-21 22:09:08 -08:00
parent 0af52e5178
commit 1cd729c820
14 changed files with 227 additions and 60 deletions

View File

@ -61,7 +61,7 @@ export const LightTheme = {
alertText: '#ff8888', alertText: '#ff8888',
itemBorder: '#eeeeee', itemBorder: '#eeeeee',
inputBorder: '#888888', inputBorder: '#888888',
sectionBorder: '#dddddd', sectionBorder: '#bbbbbb',
headerBorder: '#aaaaaa', headerBorder: '#aaaaaa',
}; };

View File

@ -1,6 +1,6 @@
export const en = { export const en = {
code: 'en', code: 'en',
account: 'Account', settings: 'Settings',
contacts: 'Contacts', contacts: 'Contacts',
logout: 'Logout', logout: 'Logout',
confirmLogout: 'Are you sure you want to logout?', confirmLogout: 'Are you sure you want to logout?',
@ -29,7 +29,7 @@ export const en = {
export const fr = { export const fr = {
code: 'fr', code: 'fr',
account: 'Compte', settings: 'Paramètres',
contacts: 'Contacts', contacts: 'Contacts',
logout: 'Déconnexion', logout: 'Déconnexion',
confirmLogout: 'Êtes-vous sûr de vouloir vous déconnecter?', confirmLogout: 'Êtes-vous sûr de vouloir vous déconnecter?',

View File

@ -9,10 +9,14 @@ export function useSettingsContext() {
width: null, width: null,
height: null, height: null,
theme: null, theme: null,
setTheme: null,
colors: {}, colors: {},
menuStyle: {}, menuStyle: {},
language: 'en', languages: [{ value: null, label: 'Default' }, { value: 'en', label: 'English' }, { value: 'fr', label: 'Français' }],
language: null,
strings: en, strings: en,
dateFormat: 'mm/dd',
timeFormat: '12h',
}); });
const SMALL_MEDIUM = 650; const SMALL_MEDIUM = 650;
@ -20,8 +24,6 @@ export function useSettingsContext() {
const LARGE_XLARGE = 1600; const LARGE_XLARGE = 1600;
const updateState = (value) => { const updateState = (value) => {
console.log("VALUE: ", value);
setState((s) => ({ ...s, ...value })); setState((s) => ({ ...s, ...value }));
}; };
@ -50,27 +52,52 @@ console.log("VALUE: ", value);
const scheme = localStorage.getItem('color_scheme'); const scheme = localStorage.getItem('color_scheme');
if (scheme === 'dark') { if (scheme === 'dark') {
updateState({ theme: scheme, colors: DarkTheme, menuStyle: { backgroundColor: DarkTheme.modalArea, color: DarkTheme.mainText } }); updateState({ theme: scheme, scheme: 'dark', colors: DarkTheme, menuStyle: { backgroundColor: DarkTheme.modalArea, color: DarkTheme.mainText } });
} }
else if (scheme === 'light') { else if (scheme === 'light') {
updateState({ theme: scheme, colors: LightTheme, menuStyle: { backgroundColor: LightTheme.modalArea, color: LightTheme.mainText } }) updateState({ theme: scheme, scheme: 'light', colors: LightTheme, menuStyle: { backgroundColor: LightTheme.modalArea, color: LightTheme.mainText } })
} }
else { else {
if(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { if(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
updateState({ theme: 'dark', colors: DarkTheme, menuStyle: { backgroundColor: DarkTheme.modalArea, color: DarkTheme.mainText } }); updateState({ theme: null, scheme: 'dark', colors: DarkTheme, menuStyle: { backgroundColor: DarkTheme.modalArea, color: DarkTheme.mainText } });
} }
else { else {
updateState({ theme: 'light', colors: LightTheme, menuStyle: { backgroundColor: LightTheme.modalArea, color: LightTheme.mainText } }); updateState({ theme: null, scheme: 'light', colors: LightTheme, menuStyle: { backgroundColor: LightTheme.modalArea, color: LightTheme.mainText } });
} }
} }
const timeFormat = localStorage.getItem('time_format');
if (timeFormat == '24h') {
updateState({ timeFormat });
}
else {
updateState({ timeFormat: '12h' });
}
const dateFormat = localStorage.getItem('date_format');
if (dateFormat == 'dd/mm') {
updateState({ dateFormat });
}
else {
updateState({ dateFormat: 'mm/dd' });
}
const language = localStorage.getItem('language'); const language = localStorage.getItem('language');
if (language && language.startsWith('fr')) { if (language && language.startsWith('fr')) {
updateState({ language: 'fr', strings: fr }); updateState({ language: 'fr', strings: fr });
} }
else { else if (language && language.startWith('en')) {
updateState({ language: 'en', strings: en }); updateState({ language: 'en', strings: en });
} }
else {
const browser = navigator.language;
if (browser && browser.startsWith('fr')) {
updateState({ language: null, strings: fr });
}
else {
updateState({ language: null, strings: en });
}
}
return () => { return () => {
window.removeEventListener('resize', handleResize); window.removeEventListener('resize', handleResize);
@ -80,31 +107,52 @@ console.log("VALUE: ", value);
}, []); }, []);
const actions = { const actions = {
setDarkTheme: () => { setTheme: (theme) => {
if (theme === 'dark') {
localStorage.setItem('color_scheme', 'dark'); localStorage.setItem('color_scheme', 'dark');
updateState({ theme: 'dark', colors: DarkTheme, menuStyle: { backgroundColor: DarkTheme.modalArea, color: DarkTheme.mainText } }); updateState({ theme: 'dark', scheme: 'dark', colors: DarkTheme, menuStyle: { backgroundColor: DarkTheme.modalArea, color: DarkTheme.mainText } });
}, }
setLightTheme : () => { else if (theme === 'light') {
localStorage.setItem('color_scheme', 'light'); localStorage.setItem('color_scheme', 'light');
updateState({ theme: 'light', colors: LightTheme, menuStyle: { backgroundColor: LightTheme.modalArea, color: LightTheme.mainText } }); updateState({ theme: 'light', scheme: 'light', colors: LightTheme, menuStyle: { backgroundColor: LightTheme.modalArea, color: LightTheme.mainText } });
},
setDefaultTheme: () => {
localStorage.clearItem('color_scheme');
if(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
updateState({ theme: 'dark', colors: DarkTheme, menuStyle: { backgroundColor: DarkTheme.modalArea, color: DarkTheme.mainText } });
} }
else { else {
updateState({ theme: 'light', colors: LightTheme, menuStyle: { backgroundColor: LightTheme.modalArea, color: LightTheme.mainText } }); localStorage.removeItem('color_scheme');
if(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
updateState({ theme: null, scheme: 'dark', colors: DarkTheme, menuStyle: { backgroundColor: DarkTheme.modalArea, color: DarkTheme.mainText } });
}
else {
updateState({ theme: null, scheme: 'ligth', colors: LightTheme, menuStyle: { backgroundColor: LightTheme.modalArea, color: LightTheme.mainText } });
}
} }
}, },
setLanguage: (code: string) => { setLanguage: (code: string) => {
localStorage.setItem('language', code);
if (code && code.startsWith('fr')) { if (code && code.startsWith('fr')) {
localStorage.setItem('language', 'fr');
updateState({ language: 'fr', strings: fr }); updateState({ language: 'fr', strings: fr });
} }
else { else if (code && code.startsWith('en')) {
localStorage.setItem('language', 'en');
updateState({ language: 'en', strings: en }); updateState({ language: 'en', strings: en });
} }
else {
localStorage.removeItem('language');
const browser = navigator.language;
if (browser && browser.startsWith('fr')) {
updateState({ language: null, strings: fr });
}
else {
updateState({ language: null, strings: en });
}
}
},
setDateFormat: (dateFormat) => {
localStorage.setItem('date_format', dateFormat);
updateState({ dateFormat });
},
setTimeFormat: (timeFormat) => {
localStorage.setItem('time_format', timeFormat);
updateState({ timeFormat });
}, },
} }

View File

@ -191,6 +191,7 @@ export const SessionWrapper = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: relative; position: relative;
background-color: ${props => props.theme.baseArea};
} }
} }
@ -217,6 +218,7 @@ export const SessionWrapper = styled.div`
.right { .right {
flex-grow: 1; flex-grow: 1;
position: relative; position: relative;
background-color: ${props => props.theme.baseArea};
.drawer { .drawer {
padding: 0px; padding: 0px;

View File

@ -8,7 +8,7 @@ export function Account({ closeAccount, openProfile }) {
return ( return (
<AccountWrapper> <AccountWrapper>
<div className="header"> <div className="header">
<div className="label">Account</div> <div className="label">Settings</div>
<div className="dismiss" onClick={closeAccount}> <div className="dismiss" onClick={closeAccount}>
<DoubleRightOutlined /> <DoubleRightOutlined />
</div> </div>

View File

@ -39,7 +39,7 @@ export const AccountWrapper = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
padding-top: 32px; padding-top: 16px;
align-items: center; align-items: center;
flex-grow: 1; flex-grow: 1;

View File

@ -101,7 +101,7 @@ export function Profile({ closeProfile }) {
{ state.display !== 'xlarge' && ( { state.display !== 'xlarge' && (
<div className="rightHeader"> <div className="rightHeader">
<div className="title">{ state.handle }</div> <div className="title">{ state.handle }</div>
<div className="section">Profile Settings</div> <div className="section">Profile</div>
</div> </div>
)} )}
<div className={ state.display === 'xlarge' ? 'midContent' : 'rightContent' }> <div className={ state.display === 'xlarge' ? 'midContent' : 'rightContent' }>
@ -144,9 +144,7 @@ export function Profile({ closeProfile }) {
</div> </div>
</div> </div>
{ state.display !== 'xlarge' && ( { state.display !== 'xlarge' && (
<div className="account"> <div>
<div className="section">Account Settings</div>
<div className="controls">
<AccountAccess /> <AccountAccess />
{ state.display === 'small' && ( { state.display === 'small' && (
<div className="logout" onClick={logout}> <div className="logout" onClick={logout}>
@ -155,7 +153,6 @@ export function Profile({ closeProfile }) {
</div> </div>
)} )}
</div> </div>
</div>
)} )}
<Modal title="Profile Image" centered visible={state.editProfileImage} footer={editImageFooter} <Modal title="Profile Image" centered visible={state.editProfileImage} footer={editImageFooter}
bodyStyle={{ padding: 16 }} onCancel={actions.clearEditProfileImage}> bodyStyle={{ padding: 16 }} onCancel={actions.clearEditProfileImage}>

View File

@ -61,7 +61,9 @@ export const ProfileWrapper = styled.div`
.logo { .logo {
position: relative; position: relative;
width: 20vw; width: 256px;
height: 256px;
flex-shrink: 0;
cursor: pointer; cursor: pointer;
margin-left: 32px; margin-left: 32px;
margin-right: 32px; margin-right: 32px;
@ -94,7 +96,9 @@ export const ProfileWrapper = styled.div`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
padding-top: 32px; padding-top: 64px;
padding-left: 32px;
align-items: center;
} }
.rightContent { .rightContent {
@ -153,6 +157,7 @@ export const ProfileWrapper = styled.div`
.data { .data {
padding-left: 8px; padding-left: 8px;
margin-top: -4px;
} }
} }
@ -164,6 +169,7 @@ export const ProfileWrapper = styled.div`
.data { .data {
padding-left: 8px; padding-left: 8px;
margin-top: -4px;
} }
} }
} }

View File

@ -1,6 +1,6 @@
import { AccountAccessWrapper, SealModal, EditFooter } from './AccountAccess.styled'; import { AccountAccessWrapper, SealModal, EditFooter } from './AccountAccess.styled';
import { useAccountAccess } from './useAccountAccess.hook'; import { useAccountAccess } from './useAccountAccess.hook';
import { Button, Modal, Switch, Form, Input } from 'antd'; import { Button, Modal, Switch, Form, Input, Radio, Select } from 'antd';
import { SettingOutlined, UserOutlined, LockOutlined, ExclamationCircleOutlined } from '@ant-design/icons'; import { SettingOutlined, UserOutlined, LockOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
export function AccountAccess() { export function AccountAccess() {
@ -79,6 +79,52 @@ export function AccountAccess() {
return ( return (
<AccountAccessWrapper> <AccountAccessWrapper>
{ modalContext } { modalContext }
<div className="account">
<div className="section">Application</div>
<div className="controls">
<div className="option">
<div className="label">Time Format</div>
<Radio.Group onChange={actions.setTimeFormat} value={state.timeFormat}>
<Radio value={'12h'}>12h</Radio>
<Radio value={'24h'}>24h</Radio>
</Radio.Group>
</div>
<div className="option">
<div className="label">Date Format</div>
<Radio.Group onChange={actions.setDateFormat} value={state.dateFormat}>
<Radio value={'mm/dd'}>mm/dd</Radio>
<Radio value={'dd/mm'}>dd/mm</Radio>
</Radio.Group>
</div>
<div className="option">
<div className="label">Theme</div>
<Select
defaultValue={null}
style={{ width: 128 }}
value={state.theme}
onChange={actions.setTheme}
options={[
{ value: null, label: 'Default' },
{ value: 'light', label: 'Light' },
{ value: 'dark', label: 'Dark' },
]}
/>
</div>
<div className="option">
<div className="label">Language</div>
<Select
defaultValue={null}
style={{ width: 128 }}
value={state.language}
onChange={actions.setLanguage}
options={state.languages}
/>
</div>
</div>
</div>
<div className="account">
<div className="section">Account</div>
<div className="controls">
<div className="switch"> <div className="switch">
<Switch size="small" checked={state.searchable} onChange={enable => saveSearchable(enable)} /> <Switch size="small" checked={state.searchable} onChange={enable => saveSearchable(enable)} />
<div className="switchLabel">Visible in Registry &nbsp;&nbsp;</div> <div className="switchLabel">Visible in Registry &nbsp;&nbsp;</div>
@ -91,6 +137,8 @@ export function AccountAccess() {
<LockOutlined /> <LockOutlined />
<div className="label">Change Login</div> <div className="label">Change Login</div>
</div> </div>
</div>
</div>
<Modal title="Topic Sealing Key" centered visible={state.editSeal} footer={editSealFooter} onCancel={actions.clearEditSeal} bodyStyle={{ padding: 16 }}> <Modal title="Topic Sealing Key" centered visible={state.editSeal} footer={editSealFooter} onCancel={actions.clearEditSeal} bodyStyle={{ padding: 16 }}>
<SealModal> <SealModal>
<div className="switch"> <div className="switch">

View File

@ -7,6 +7,45 @@ export const AccountAccessWrapper = styled.div`
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding-bottom: 8px; padding-bottom: 8px;
width: 100%;
.account {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.controls {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
padding: 8px;
width: fit-content;
.option {
display: flex;
padding-top: 8px;
align-items: center;
.label {
padding-right: 16px;
min-width: 110px;
}
}
}
.section {
width: 100%;
color: ${props => props.theme.hintText};
padding-top: 24px;
font-size: 12px;
display: flex;
widtH: 75%;
justify-content: center;
border-bottom: 1px solid ${props => props.theme.sectionBorder};
}
.switch { .switch {
display: flex; display: flex;

View File

@ -1,6 +1,7 @@
import { useRef, useState, useEffect, useContext } from 'react'; import { useRef, useState, useEffect, useContext } from 'react';
import { AccountContext } from 'context/AccountContext'; import { AccountContext } from 'context/AccountContext';
import { ProfileContext } from 'context/ProfileContext'; import { ProfileContext } from 'context/ProfileContext';
import { SettingsContext } from 'context/SettingsContext';
import { generateSeal, unlockSeal, updateSeal } from 'context/sealUtil'; import { generateSeal, unlockSeal, updateSeal } from 'context/sealUtil';
import { getUsername } from 'api/getUsername'; import { getUsername } from 'api/getUsername';
export function useAccountAccess() { export function useAccountAccess() {
@ -25,12 +26,19 @@ export function useAccountAccess() {
sealDelete: null, sealDelete: null,
sealUnlock: null, sealUnlock: null,
timeFormat: '12h',
dateFormat: 'mm/dd',
theme: null,
language: null,
languages: [],
seal: null, seal: null,
sealKey: null, sealKey: null,
}); });
const profile = useContext(ProfileContext); const profile = useContext(ProfileContext);
const account = useContext(AccountContext); const account = useContext(AccountContext);
const settings = useContext(SettingsContext);
const debounce = useRef(null); const debounce = useRef(null);
const updateState = (value) => { const updateState = (value) => {
@ -47,6 +55,11 @@ export function useAccountAccess() {
updateState({ searchable: status.searchable, seal, sealKey }); updateState({ searchable: status.searchable, seal, sealKey });
}, [account.state]); }, [account.state]);
useEffect(() => {
const { timeFormat, dateFormat, theme, language, languages } = settings.state;
updateState({ timeFormat, dateFormat, theme, language, languages });
}, [settings.state]);
const sealUnlock = async () => { const sealUnlock = async () => {
const unlocked = unlockSeal(state.seal, state.sealUnlock); const unlocked = unlockSeal(state.seal, state.sealUnlock);
await account.actions.unlockSeal(unlocked); await account.actions.unlockSeal(unlocked);
@ -85,6 +98,20 @@ export function useAccountAccess() {
} }
const actions = { const actions = {
setTimeFormat: (timeFormat) => {
console.log("TIME", timeFormat);
settings.actions.setTimeFormat(timeFormat.target.value);
},
setDateFormat: (dateFormat) => {
settings.actions.setDateFormat(dateFormat.target.value);
},
setTheme: (theme) => {
settings.actions.setTheme(theme);
},
setLanguage: (language) => {
settings.actions.setLanguage(language);
},
setEditSeal: () => { setEditSeal: () => {
let sealMode; let sealMode;
let sealEnabled = isEnabled(); let sealEnabled = isEnabled();

View File

@ -32,10 +32,10 @@ export function Identity({ openAccount, openCards, cardUpdated }) {
const menu = ( const menu = (
<Menu style={state.menuStyle}> <Menu style={state.menuStyle}>
<Menu.Item style={state.menuStyle} key="0"> <Menu.Item style={state.menuStyle} key="0">
<div onClick={openAccount}>{ state.strings.account }</div> <div onClick={openCards}>{ state.strings.contacts }</div>
</Menu.Item> </Menu.Item>
<Menu.Item style={state.menuStyle} key="1"> <Menu.Item style={state.menuStyle} key="1">
<div onClick={openCards}>{ state.strings.contacts }</div> <div onClick={openAccount}>{ state.strings.settings }</div>
</Menu.Item> </Menu.Item>
<Menu.Item style={state.menuStyle} key="2"> <Menu.Item style={state.menuStyle} key="2">
<div onClick={logout}>{ state.strings.logout }</div> <div onClick={logout}>{ state.strings.logout }</div>

View File

@ -16,10 +16,10 @@ export function Welcome() {
<div className="header">Databag</div> <div className="header">Databag</div>
<div>{ state.strings.communication }</div> <div>{ state.strings.communication }</div>
</div> </div>
{ state.theme === 'light' && ( { state.scheme === 'light' && (
<img className="session" src={light} alt="Session Background" /> <img className="session" src={light} alt="Session Background" />
)} )}
{ state.theme === 'dark' && ( { state.scheme === 'dark' && (
<img className="session" src={dark} alt="Session Background" /> <img className="session" src={dark} alt="Session Background" />
)} )}
<div className="message"> <div className="message">

View File

@ -4,7 +4,7 @@ import { SettingsContext } from 'context/SettingsContext';
export function useWelcome() { export function useWelcome() {
const [state, setState] = useState({ const [state, setState] = useState({
theme: null, scheme: null,
strings: {}, strings: {},
}); });
@ -15,8 +15,8 @@ export function useWelcome() {
} }
useEffect(() => { useEffect(() => {
const { theme, strings } = settings.state; const { scheme, strings } = settings.state;
updateState({ theme, strings }); updateState({ scheme, strings });
}, [settings.state]); }, [settings.state]);
const actions = {}; const actions = {};