formatting source

This commit is contained in:
Roland Osborne 2024-09-10 15:03:47 -07:00
parent 674d0f5ae2
commit d539fdf0bb
11 changed files with 533 additions and 357 deletions

View File

@ -88,7 +88,7 @@ const theme = createTheme({
'#559e83',
'#559e83',
],
'light-databag-green': [
'light-databag-green': [
'#888888',
'#448844',
'#448844',

View File

@ -22,7 +22,7 @@ import {
IconServer,
IconKey,
} from '@tabler/icons-react'
import { modals } from '@mantine/modals';
import { modals } from '@mantine/modals'
export function Access() {
const { state, actions } = useAccess()
@ -65,12 +65,10 @@ export function Access() {
backgroundOpacity: 0.55,
blur: 3,
},
children: (
<div>{state.strings.tryAgain}</div>
),
children: <div>{state.strings.tryAgain}</div>,
cancelProps: { display: 'none' },
confirmProps: { display: 'none' },
});
})
}
}
actions.setLoading(false)

View File

@ -45,7 +45,7 @@ export function useAccess() {
}
const { protocol, host } = location
updateState({ host, secure: protocol === 'https' });
updateState({ host, secure: protocol === 'https' })
}, [])
useEffect(() => {
@ -121,8 +121,9 @@ export function useAccess() {
updateState({ code })
},
setNode: (host: string) => {
const insecure = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|:\d+$|$)){4}$/.test(host);
updateState({ host, secure: !insecure });
const insecure =
/^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|:\d+$|$)){4}$/.test(host)
updateState({ host, secure: !insecure })
},
setLanguage: (code: string) => {
display.actions.setLanguage(code)

View File

@ -53,12 +53,12 @@ export function useAppContext() {
code,
params
)
updateState({ session: login });
updateState({ session: login })
},
accountLogout: async () => {
if (state.session) {
await sdk.current.logout(state.session, false)
updateState({ session: null });
updateState({ session: null })
}
},
accountCreate: async (
@ -87,7 +87,7 @@ export function useAppContext() {
token,
params
)
updateState({ session });
updateState({ session })
},
accountAccess: async (node: string, secure: boolean, token: string) => {
const params = {
@ -102,7 +102,7 @@ export function useAppContext() {
appName: 'databag',
}
const session = await sdk.current.access(node, secure, token, params)
updateState({ session });
updateState({ session })
},
getAvailable: async (node: string, secure: boolean) => {
return await sdk.current.available(node, secure)

View File

@ -1,32 +1,43 @@
import { useState } from 'react';
import classes from './Identity.module.css';
import { useIdentity } from './useIdentity.hook';
import { Text, Image, Menu, Switch } from '@mantine/core';
import { useState } from 'react'
import classes from './Identity.module.css'
import { useIdentity } from './useIdentity.hook'
import { Text, Image, Menu, Switch } from '@mantine/core'
import {
IconLogout,
IconChevronRight,
IconSettings,
IconAddressBook
IconAddressBook,
} from '@tabler/icons-react'
import { modals } from '@mantine/modals';
import { modals } from '@mantine/modals'
export function Identity({ settings, contacts }: { settings: () => void, contacts: () => void }) {
const { state, actions } = useIdentity();
const [all, setAll] = useState(false);
export function Identity({
settings,
contacts,
}: {
settings: () => void
contacts: () => void
}) {
const { state, actions } = useIdentity()
const [all, setAll] = useState(false)
const logout = () => modals.openConfirmModal({
title: state.strings.confirmLogout,
withCloseButton: false,
overlayProps: {
backgroundOpacity: 0.55,
blur: 3,
},
children: (
<Switch label={state.strings.allDevices} size="md" onChange={(ev) => actions.setAll(ev.currentTarget.checked)} />
),
labels: { confirm: state.strings.logout, cancel: state.strings.cancel },
onConfirm: actions.logout,
});
const logout = () =>
modals.openConfirmModal({
title: state.strings.confirmLogout,
withCloseButton: false,
overlayProps: {
backgroundOpacity: 0.55,
blur: 3,
},
children: (
<Switch
label={state.strings.allDevices}
size="md"
onChange={(ev) => actions.setAll(ev.currentTarget.checked)}
/>
),
labels: { confirm: state.strings.logout, cancel: state.strings.cancel },
onConfirm: actions.logout,
})
return (
<Menu shadow="md" position="right">
@ -34,22 +45,30 @@ export function Identity({ settings, contacts }: { settings: () => void, contact
<div className={classes.identity}>
<Image radius="sm" className={classes.image} src={state.imageUrl} />
<div className={classes.text}>
{ !state.profile.name && (
{!state.profile.name && (
<Text className={classes.nameUnset}>{state.strings.name}</Text>
)}
{ state.profile.name && (
{state.profile.name && (
<Text className={classes.nameSet}>{state.profile.name}</Text>
)}
<Text className={classes.handle}>{`${state.profile.handle}${state.profile.node ? '/' + state.profile.node : ''}`}</Text>
<Text
className={classes.handle}
>{`${state.profile.handle}${state.profile.node ? '/' + state.profile.node : ''}`}</Text>
</div>
<IconChevronRight className={classes.icon} />
</div>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item onClick={settings} leftSection={<IconSettings />}>{ state.strings.settings }</Menu.Item>
<Menu.Item leftSection={<IconAddressBook />}>{ state.strings.contacts }</Menu.Item>
<Menu.Item onClick={logout} leftSection={<IconLogout />}>{ state.strings.logout }</Menu.Item>
<Menu.Item onClick={settings} leftSection={<IconSettings />}>
{state.strings.settings}
</Menu.Item>
<Menu.Item leftSection={<IconAddressBook />}>
{state.strings.contacts}
</Menu.Item>
<Menu.Item onClick={logout} leftSection={<IconLogout />}>
{state.strings.logout}
</Menu.Item>
</Menu.Dropdown>
</Menu>
)

View File

@ -2,7 +2,7 @@ import { useState, useContext, useEffect } from 'react'
import { DisplayContext } from '../context/DisplayContext'
import { AppContext } from '../context/AppContext'
import { ContextType } from '../context/ContextType'
import { Profile } from 'databag-client-sdk';
import { Profile } from 'databag-client-sdk'
export function useIdentity() {
const app = useContext(AppContext) as ContextType
@ -13,7 +13,7 @@ export function useIdentity() {
profile: {} as Profile,
profileSet: false,
imageUrl: null,
});
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => {
@ -21,29 +21,32 @@ export function useIdentity() {
}
useEffect(() => {
const identity = app.state.session?.getIdentity();
const identity = app.state.session?.getIdentity()
if (!identity) {
console.log('session not set in identity hook');
}
else {
console.log('session not set in identity hook')
} else {
const setProfile = (profile: Profile) => {
updateState({ profile, profileSet: true, imageUrl: identity.getProfileImageUrl() })
updateState({
profile,
profileSet: true,
imageUrl: identity.getProfileImageUrl(),
})
}
identity.addProfileListener(setProfile)
return () => {
identity.removeProfileListener(setProfile);
identity.removeProfileListener(setProfile)
}
}
}, []);
}, [])
const actions = {
setAll: (all: boolean) => {
updateState({ all });
updateState({ all })
},
logout: async () => {
await app.actions.accountLogout(state.all);
await app.actions.accountLogout(state.all)
},
};
}
return { state, actions };
return { state, actions }
}

View File

@ -25,16 +25,15 @@ export function useRoot() {
useEffect(() => {
const { pathname, node, session } = app.state || {}
const path = pathname === '/session' || pathname === '/node' || pathname === '/access' ? pathname : '/';
const path =
pathname === '/session' || pathname === '/node' || pathname === '/access'
? pathname
: '/'
if (path === '/session' && !session) {
navigate('/')
} else if (path === '/node' && !node) {
navigate('/')
} else if (
path === '/' &&
!session &&
!node
) {
} else if (path === '/' && !session && !node) {
navigate('/access')
} else if (path !== '/node' && node) {
navigate('/node')

View File

@ -13,17 +13,16 @@
height: 100%;
width: 33%;
min-width: 325px;
background: var(--mantine-color-surface-2);
background: var(--mantine-color-surface-2);
}
.right {
height: 100%;
display: flex;
flex-grow: 1;
background: var(--mantine-color-surface-3);
background: var(--mantine-color-surface-3);
}
}
.screen {
width: 100%;
@ -33,7 +32,7 @@
background-color: var(--mantine-color-surface-1);
overflow: auto;
padding-bottom: 16px;
}
}
.tabs {
position: absolute;
@ -48,8 +47,8 @@
display: flex;
align-items: center;
justify-content: center;
background: var(--mantine-color-tab-2);
color: var(--mantine-color-dbgreen-1);
background: var(--mantine-color-tab-2);
color: var(--mantine-color-dbgreen-1);
padding-bottom: 8px;
padding-top: 8px;
cursor: pointer;
@ -61,14 +60,14 @@
display: flex;
align-items: center;
justify-content: center;
background: var(--mantine-color-tab-1);
color: var(--mantine-color-dbgreen-0);
background: var(--mantine-color-tab-1);
color: var(--mantine-color-dbgreen-0);
padding-bottom: 8px;
padding-top: 8px;
cursor: pointer;
&:hover {
background: var(--mantine-color-tab-3);
background: var(--mantine-color-tab-3);
}
}

View File

@ -1,72 +1,99 @@
import React, { useState, useContext } from 'react'
import { Text, Drawer, Button } from '@mantine/core'
import { AppContext } from '../context/AppContext'
import { DisplayContext } from '../context/DisplayContext';
import { DisplayContext } from '../context/DisplayContext'
import { ContextType } from '../context/ContextType'
import classes from './Session.module.css'
import { IconAddressBook, IconMessages, IconSettings } from '@tabler/icons-react'
import { Settings } from '../settings/Settings';
import { Identity } from '../identity/Identity';
import { useDisclosure } from '@mantine/hooks';
import {
IconAddressBook,
IconMessages,
IconSettings,
} from '@tabler/icons-react'
import { Settings } from '../settings/Settings'
import { Identity } from '../identity/Identity'
import { useDisclosure } from '@mantine/hooks'
export function Session() {
const [ tab, setTab ] = useState('channels');
const [tab, setTab] = useState('channels')
const app = useContext(AppContext) as ContextType
const display = useContext(DisplayContext) as ContextType
const [settings, { open: openSettings, close: closeSettings }] = useDisclosure(false);
const [settings, { open: openSettings, close: closeSettings }] =
useDisclosure(false)
const click = () => {
console.log("SESSION DRAWER", openSettings);
openSettings();
console.log('SESSION DRAWER', openSettings)
openSettings()
}
return (
<div className={classes.session}>
{ display.state.layout === 'small' && (
{display.state.layout === 'small' && (
<>
<div className={classes.screen}>
{ tab === 'settings' && (
<Settings showLogout={true} />
)}
{tab === 'settings' && <Settings showLogout={true} />}
</div>
<div className={classes.tabs}>
{ tab === 'channels' && (
<div className={classes.activeTabItem}><IconMessages className={classes.tabIcon} /></div>
{tab === 'channels' && (
<div className={classes.activeTabItem}>
<IconMessages className={classes.tabIcon} />
</div>
)}
{ tab !== 'channels' && (
<div className={classes.idleTabItem} onClick={() => setTab('channels')}><IconMessages className={classes.tabIcon} /></div>
{tab !== 'channels' && (
<div
className={classes.idleTabItem}
onClick={() => setTab('channels')}
>
<IconMessages className={classes.tabIcon} />
</div>
)}
<div className={classes.tabDivider} />
{ tab === 'contacts' && (
<div className={classes.activeTabItem}><IconAddressBook className={classes.tabIcon} /></div>
{tab === 'contacts' && (
<div className={classes.activeTabItem}>
<IconAddressBook className={classes.tabIcon} />
</div>
)}
{ tab !== 'contacts' && (
<div className={classes.idleTabItem} onClick={() => setTab('contacts')}><IconAddressBook className={classes.tabIcon} /></div>
{tab !== 'contacts' && (
<div
className={classes.idleTabItem}
onClick={() => setTab('contacts')}
>
<IconAddressBook className={classes.tabIcon} />
</div>
)}
<div className={classes.tabDivider} />
{ tab === 'settings' && (
<div className={classes.activeTabItem}><IconSettings className={classes.tabIcon} /></div>
{tab === 'settings' && (
<div className={classes.activeTabItem}>
<IconSettings className={classes.tabIcon} />
</div>
)}
{ tab !== 'settings' && (
<div className={classes.idleTabItem} onClick={() => setTab('settings')}><IconSettings className={classes.tabIcon} /></div>
{tab !== 'settings' && (
<div
className={classes.idleTabItem}
onClick={() => setTab('settings')}
>
<IconSettings className={classes.tabIcon} />
</div>
)}
</div>
</>
)}
{ display.state.layout === 'large' && (
{display.state.layout === 'large' && (
<div className={classes.display}>
<div className={classes.left}>
<Identity settings={click} contacts={() => {}} />
</div>
<div className={classes.right}>
</div>
<Drawer opened={settings} onClose={closeSettings} withCloseButton={false} size="sm" position="right">
<div className={classes.right}></div>
<Drawer
opened={settings}
onClose={closeSettings}
withCloseButton={false}
size="sm"
position="right"
>
<Settings showLogout={false} />
</Drawer>
</div>
)}
</div>
);
)
}

View File

@ -1,135 +1,170 @@
import { useSettings } from './useSettings.hook';
import { Modal, Textarea, TextInput, PasswordInput, Radio, Group, Select, Switch, Text, Image, Button, UnstyledButton } from '@mantine/core';
import classes from './Settings.module.css';
import { IconLock, IconUser, IconClock, IconIdBadge, IconCalendar, IconUsers, IconVideo, IconMicrophone, IconWorld, IconBrightness, IconTicket, IconCloudLock, IconBell, IconEye, IconBook, IconMapPin, IconLogout, IconLogin } from '@tabler/icons-react'
import { modals } from '@mantine/modals';
import { useSettings } from './useSettings.hook'
import {
Modal,
Textarea,
TextInput,
PasswordInput,
Radio,
Group,
Select,
Switch,
Text,
Image,
Button,
UnstyledButton,
} from '@mantine/core'
import classes from './Settings.module.css'
import {
IconLock,
IconUser,
IconClock,
IconIdBadge,
IconCalendar,
IconUsers,
IconVideo,
IconMicrophone,
IconWorld,
IconBrightness,
IconTicket,
IconCloudLock,
IconBell,
IconEye,
IconBook,
IconMapPin,
IconLogout,
IconLogin,
} from '@tabler/icons-react'
import { modals } from '@mantine/modals'
import { useDisclosure } from '@mantine/hooks'
import { useCallback, useState, useRef } from 'react'
import Cropper from 'react-easy-crop';
import { Area } from 'react-easy-crop/types';
import Cropper from 'react-easy-crop'
import { Area } from 'react-easy-crop/types'
export function Settings({ showLogout }: { showLogout: boolean }) {
const imageFile = useRef(null as null | HTMLInputElement);
const { state, actions } = useSettings();
const [changeOpened, { open: changeOpen, close: changeClose }] = useDisclosure(false)
const [detailsOpened, { open: detailsOpen, close: detailsClose }] = useDisclosure(false)
const [imageOpened, { open: imageOpen, close: imageClose }] = useDisclosure(false)
const [savingLogin, setSavingLogin] = useState(false);
const [savingDetails, setSavingDetails] = useState(false);
const [savingImage, setSavingImage] = useState(false);
const [savingRegistry, setSavingRegistry] = useState(false);
const [savingNotifications, setSavingNotifications] = useState(false);
const imageFile = useRef(null as null | HTMLInputElement)
const { state, actions } = useSettings()
const [changeOpened, { open: changeOpen, close: changeClose }] =
useDisclosure(false)
const [detailsOpened, { open: detailsOpen, close: detailsClose }] =
useDisclosure(false)
const [imageOpened, { open: imageOpen, close: imageClose }] =
useDisclosure(false)
const [savingLogin, setSavingLogin] = useState(false)
const [savingDetails, setSavingDetails] = useState(false)
const [savingImage, setSavingImage] = useState(false)
const [savingRegistry, setSavingRegistry] = useState(false)
const [savingNotifications, setSavingNotifications] = useState(false)
const logout = () => modals.openConfirmModal({
title: state.strings.confirmLogout,
withCloseButton: false,
overlayProps: {
backgroundOpacity: 0.55,
blur: 3,
},
children: (
<Switch label={state.strings.allDevices} size="md" onChange={(ev) => actions.setAll(ev.currentTarget.checked)} />
),
labels: { confirm: state.strings.logout, cancel: state.strings.cancel },
onConfirm: actions.logout,
});
const logout = () =>
modals.openConfirmModal({
title: state.strings.confirmLogout,
withCloseButton: false,
overlayProps: {
backgroundOpacity: 0.55,
blur: 3,
},
children: (
<Switch
label={state.strings.allDevices}
size="md"
onChange={(ev) => actions.setAll(ev.currentTarget.checked)}
/>
),
labels: { confirm: state.strings.logout, cancel: state.strings.cancel },
onConfirm: actions.logout,
})
const setRegistry = async (checked: boolean) => {
if (!savingRegistry) {
setSavingRegistry(true);
setSavingRegistry(true)
try {
if (checked) {
await actions.enableRegistry();
await actions.enableRegistry()
} else {
await actions.disableRegistry();
await actions.disableRegistry()
}
} catch (err) {
console.log(err)
showError()
}
catch (err) {
console.log(err);
showError();
}
setSavingRegistry(false);
setSavingRegistry(false)
}
}
const setNotifications = async (checked: boolean) => {
if (!savingNotifications) {
setSavingNotifications(true);
setSavingNotifications(true)
try {
if (checked) {
await actions.enableNotifications();
await actions.enableNotifications()
} else {
await actions.disableNotifications();
await actions.disableNotifications()
}
} catch (err) {
console.log(err)
showError()
}
catch (err) {
console.log(err);
showError();
}
setSavingNotifications(false);
setSavingNotifications(false)
}
}
const setDetails = async () => {
if (!savingDetails) {
setSavingDetails(true);
setSavingDetails(true)
try {
await actions.setDetails();
detailsClose();
await actions.setDetails()
detailsClose()
} catch (err) {
console.log(err)
showError()
}
catch (err) {
console.log(err);
showError();
}
setSavingDetails(false);
setSavingDetails(false)
}
}
const clickSelect = () => {
if (imageFile.current) {
imageFile.current.click();
imageFile.current.click()
}
}
const selectImage = (target: HTMLInputElement) => {
if (target.files) {
var reader = new FileReader();
var reader = new FileReader()
reader.onload = () => {
if (typeof reader.result === 'string') {
actions.setEditImage(reader.result);
actions.setEditImage(reader.result)
}
}
reader.readAsDataURL(target.files[0]);
reader.readAsDataURL(target.files[0])
}
}
const setImage = async () => {
if (!savingImage) {
setSavingImage(true);
setSavingImage(true)
try {
await actions.setImage();
imageClose();
await actions.setImage()
imageClose()
} catch (err) {
console.log(err)
showError()
}
catch (err) {
console.log(err);
showError();
}
setSavingImage(false);
setSavingImage(false)
}
}
const setLogin = async () => {
if (!savingLogin) {
setSavingLogin(true);
if (!savingLogin) {
setSavingLogin(true)
try {
await actions.setLogin();
changeClose();
await actions.setLogin()
changeClose()
} catch (err) {
console.log(err)
showError()
}
catch (err) {
console.log(err);
showError();
}
setSavingLogin(false);
setSavingLogin(false)
}
}
@ -141,58 +176,71 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
backgroundOpacity: 0.55,
blur: 3,
},
children: (
<Text>{state.strings.tryAgain}</Text>
),
children: <Text>{state.strings.tryAgain}</Text>,
cancelProps: { display: 'none' },
confirmProps: { display: 'none' },
});
})
}
const onCropComplete = useCallback((crop: Area) => {
actions.setEditImageCrop(crop)
// eslint-disable-next-line
}, []);
}, [])
return (
<>
{ state.profileSet && (
{state.profileSet && (
<div className={classes.settings}>
<Text className={classes.header}>{`${state.profile.handle}${state.profile.node ? '/' + state.profile.node : ''}`}</Text>
<Text
className={classes.header}
>{`${state.profile.handle}${state.profile.node ? '/' + state.profile.node : ''}`}</Text>
<div className={classes.image}>
{ state.profile.imageSet && (
{state.profile.imageSet && (
<div className={classes.imageSet}>
<Image radius="md" src={state.imageUrl} />
<Image radius="md" src={state.imageUrl} />
<div className={classes.edit}>
<Button size="compact-md" variant="outlined" onClick={imageOpen}>{ state.strings.edit }</Button>
<Button
size="compact-md"
variant="outlined"
onClick={imageOpen}
>
{state.strings.edit}
</Button>
</div>
</div>
)}
{ !state.profile.imageSet && (
{!state.profile.imageSet && (
<div className={classes.imageUnset} onClick={imageOpen}>
<Image radius="md" src={state.imageUrl} />
<Text className={classes.unsetEdit}>{ state.strings.edit }</Text>
<Image radius="md" src={state.imageUrl} />
<Text className={classes.unsetEdit}>{state.strings.edit}</Text>
</div>
)}
</div>
<div className={classes.section}>
<div className={classes.divider} />
<UnstyledButton className={classes.sectionEdit} onClick={detailsOpen}>{ state.strings.edit }</UnstyledButton>
<UnstyledButton
className={classes.sectionEdit}
onClick={detailsOpen}
>
{state.strings.edit}
</UnstyledButton>
</div>
{ !state.profile.name && (
{!state.profile.name && (
<Text className={classes.nameUnset}>{state.strings.name}</Text>
)}
{ state.profile.name && (
{state.profile.name && (
<Text className={classes.nameSet}>{state.profile.name}</Text>
)}
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconMapPin />
</div>
{ !state.profile.location && (
<Text className={classes.entryUnset}>{state.strings.location}</Text>
{!state.profile.location && (
<Text className={classes.entryUnset}>
{state.strings.location}
</Text>
)}
{ state.profile.location && (
{state.profile.location && (
<Text className={classes.entrySet}>{state.profile.location}</Text>
)}
</div>
@ -200,11 +248,15 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
<div className={classes.entryIcon}>
<IconBook />
</div>
{ !state.profile.description && (
<Text className={classes.entryUnset}>{state.strings.description}</Text>
{!state.profile.description && (
<Text className={classes.entryUnset}>
{state.strings.description}
</Text>
)}
{ state.profile.description && (
<Text className={classes.entrySet}>{state.profile.description}</Text>
{state.profile.description && (
<Text className={classes.entrySet}>
{state.profile.description}
</Text>
)}
</div>
<div className={classes.divider} />
@ -212,43 +264,59 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
<div className={classes.entryIcon}>
<IconEye />
</div>
<Text className={classes.entryLabel}>{ state.strings.registry }</Text>
<Switch className={classes.entryControl} checked={state.config.searchable} onChange={(ev) => setRegistry(ev.currentTarget.checked)} />
<Text className={classes.entryLabel}>{state.strings.registry}</Text>
<Switch
className={classes.entryControl}
checked={state.config.searchable}
onChange={(ev) => setRegistry(ev.currentTarget.checked)}
/>
</div>
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconCloudLock />
</div>
<Text className={classes.entryLabel}>{ state.strings.manageTopics }</Text>
<Text className={classes.entryLabel}>
{state.strings.manageTopics}
</Text>
</div>
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconBell />
</div>
<Text className={classes.entryLabel}>{ state.strings.enableNotifications }</Text>
<Switch className={classes.entryControl} checked={state.config.pushEnabled} onChange={(ev) => setNotifications(ev.currentTarget.checked)} />
<Text className={classes.entryLabel}>
{state.strings.enableNotifications}
</Text>
<Switch
className={classes.entryControl}
checked={state.config.pushEnabled}
onChange={(ev) => setNotifications(ev.currentTarget.checked)}
/>
</div>
<div className={classes.divider} />
{ showLogout && (
{showLogout && (
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconLogout />
</div>
<Text className={classes.entryLabel} onClick={logout}>{ state.strings.logout }</Text>
<Text className={classes.entryLabel} onClick={logout}>
{state.strings.logout}
</Text>
</div>
)}
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconTicket />
</div>
<Text className={classes.entryLabel}>{ state.strings.mfaTitle }</Text>
<Text className={classes.entryLabel}>{state.strings.mfaTitle}</Text>
<Switch className={classes.entryControl} />
</div>
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconLogin />
</div>
<Text className={classes.entryLabel} onClick={changeOpen}>{ state.strings.changeLogin }</Text>
<Text className={classes.entryLabel} onClick={changeOpen}>
{state.strings.changeLogin}
</Text>
</div>
<div className={classes.divider} />
<div className={classes.selects}>
@ -256,7 +324,9 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
<div className={classes.entryIcon}>
<IconClock />
</div>
<Text className={classes.controlLabel}>{ state.strings.timeFormat }</Text>
<Text className={classes.controlLabel}>
{state.strings.timeFormat}
</Text>
<Radio.Group
name="timeFormat"
className={classes.radio}
@ -264,8 +334,8 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
onChange={actions.setTimeFormat}
>
<Group mt="xs">
<Radio value="12h" label={ state.strings.timeUs } />
<Radio value="24h" label={ state.strings.timeEu } />
<Radio value="12h" label={state.strings.timeUs} />
<Radio value="24h" label={state.strings.timeEu} />
</Group>
</Radio.Group>
</div>
@ -273,7 +343,9 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
<div className={classes.entryIcon}>
<IconCalendar />
</div>
<Text className={classes.controlLabel}>{ state.strings.dateFormat }</Text>
<Text className={classes.controlLabel}>
{state.strings.dateFormat}
</Text>
<Radio.Group
name="dateFormat"
className={classes.radio}
@ -281,8 +353,8 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
onChange={actions.setDateFormat}
>
<Group mt="xs">
<Radio value="mm/dd" label={ state.strings.dateUs } />
<Radio value="dd/mm" label={ state.strings.dateEu } />
<Radio value="mm/dd" label={state.strings.dateUs} />
<Radio value="dd/mm" label={state.strings.dateEu} />
</Group>
</Radio.Group>
</div>
@ -290,7 +362,9 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
<div className={classes.entryIcon}>
<IconBrightness />
</div>
<Text className={classes.controlLabel}>{ state.strings.theme }</Text>
<Text className={classes.controlLabel}>
{state.strings.theme}
</Text>
<Select
className={classes.entryControl}
size="xs"
@ -303,7 +377,9 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
<div className={classes.entryIcon}>
<IconWorld />
</div>
<Text className={classes.controlLabel}>{ state.strings.language }</Text>
<Text className={classes.controlLabel}>
{state.strings.language}
</Text>
<Select
className={classes.entryControl}
size="xs"
@ -316,11 +392,16 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
<div className={classes.entryIcon}>
<IconMicrophone />
</div>
<Text className={classes.controlLabel}>{ state.strings.microphone }</Text>
<Text className={classes.controlLabel}>
{state.strings.microphone}
</Text>
<Select
className={classes.entryControl}
size="xs"
data={[ { value: '', label: state.strings.default }, ...state.audioInputs ]}
data={[
{ value: '', label: state.strings.default },
...state.audioInputs,
]}
value={state.audioId ? state.audioId : ''}
onChange={actions.setAudio}
/>
@ -329,11 +410,16 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
<div className={classes.entryIcon}>
<IconVideo />
</div>
<Text className={classes.controlLabel}>{ state.strings.camera }</Text>
<Text className={classes.controlLabel}>
{state.strings.camera}
</Text>
<Select
className={classes.entryControl}
size="xs"
data={[ { value: '', label: state.strings.default }, ...state.videoInputs ]}
data={[
{ value: '', label: state.strings.default },
...state.videoInputs,
]}
value={state.videoId ? state.videoId : ''}
onChange={actions.setVideo}
/>
@ -357,9 +443,7 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
leftSection={<IconUser />}
rightSection={state.taken ? <IconUsers /> : null}
placeholder={state.strings.username}
onChange={(event) =>
actions.setHandle(event.currentTarget.value)
}
onChange={(event) => actions.setHandle(event.currentTarget.value)}
error={state.taken}
/>
<PasswordInput
@ -368,9 +452,7 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
value={state.password}
leftSection={<IconLock />}
placeholder={state.strings.password}
onChange={(event) =>
actions.setPassword(event.currentTarget.value)
}
onChange={(event) => actions.setPassword(event.currentTarget.value)}
/>
<PasswordInput
className={classes.input}
@ -378,9 +460,7 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
value={state.confirm}
leftSection={<IconLock />}
placeholder={state.strings.confirmPassword}
onChange={(event) =>
actions.setConfirm(event.currentTarget.value)
}
onChange={(event) => actions.setConfirm(event.currentTarget.value)}
/>
<div className={classes.control}>
<Button variant="default" onClick={changeClose}>
@ -390,7 +470,13 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
variant="filled"
onClick={setLogin}
loading={savingLogin}
disabled={state.taken || !state.checked || !state.handle || !state.password || state.confirm !== state.password}
disabled={
state.taken ||
!state.checked ||
!state.handle ||
!state.password ||
state.confirm !== state.password
}
>
{state.strings.save}
</Button>
@ -412,9 +498,7 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
leftSectionPointerEvents="none"
leftSection={<IconIdBadge />}
placeholder={state.strings.name}
onChange={(event) =>
actions.setName(event.currentTarget.value)
}
onChange={(event) => actions.setName(event.currentTarget.value)}
/>
<TextInput
className={classes.input}
@ -423,9 +507,7 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
leftSectionPointerEvents="none"
leftSection={<IconMapPin />}
placeholder={state.strings.location}
onChange={(event) =>
actions.setLocation(event.currentTarget.value)
}
onChange={(event) => actions.setLocation(event.currentTarget.value)}
/>
<Textarea
className={classes.input}
@ -465,31 +547,43 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
>
<div className={classes.change}>
<div className={classes.cropper}>
<Cropper image={state.editImage} crop={state.crop} zoom={state.zoom} aspect={1}
onCropChange={actions.setCrop} onCropComplete={(area, crop) => onCropComplete(crop)} onZoomChange={actions.setZoom} />
<Cropper
image={state.editImage}
crop={state.crop}
zoom={state.zoom}
aspect={1}
onCropChange={actions.setCrop}
onCropComplete={(area, crop) => onCropComplete(crop)}
onZoomChange={actions.setZoom}
/>
</div>
<div className={classes.imageSelect}>
<input type='file' id='file' accept="image/*" ref={imageFile} onChange={e => selectImage(e.target)} style={{display: 'none'}}/>
<Button variant="default" className={classes.select} onClick={clickSelect}>
<input
type="file"
id="file"
accept="image/*"
ref={imageFile}
onChange={(e) => selectImage(e.target)}
style={{ display: 'none' }}
/>
<Button
variant="default"
className={classes.select}
onClick={clickSelect}
>
{state.strings.selectImage}
</Button>
<div className={classes.control}>
<Button variant="default" onClick={imageClose}>
{state.strings.cancel}
</Button>
<Button
variant="filled"
onClick={setImage}
loading={savingImage}
>
<Button variant="filled" onClick={setImage} loading={savingImage}>
{state.strings.save}
</Button>
</div>
</div>
</div>
</Modal>
</>
);
)
}

View File

@ -1,18 +1,23 @@
import { useEffect, useState, useContext, useRef } from 'react'
import { AppContext } from '../context/AppContext';
import { DisplayContext } from '../context/DisplayContext';
import { ContextType } from '../context/ContextType';
import { Session, Settings, Identity, type Profile, type Config } from 'databag-client-sdk'
import { AppContext } from '../context/AppContext'
import { DisplayContext } from '../context/DisplayContext'
import { ContextType } from '../context/ContextType'
import {
Session,
Settings,
Identity,
type Profile,
type Config,
} from 'databag-client-sdk'
import { Point, Area } from 'react-easy-crop/types'
const IMAGE_DIM = 192;
const DEBOUNCE_MS = 1000;
const IMAGE_DIM = 192
const DEBOUNCE_MS = 1000
export function useSettings() {
const display = useContext(DisplayContext) as ContextType;
const app = useContext(AppContext) as ContextType;
const debounce = useRef(setTimeout(() => {}, 0));
const display = useContext(DisplayContext) as ContextType
const app = useContext(AppContext) as ContextType
const debounce = useRef(setTimeout(() => {}, 0))
const [state, setState] = useState({
config: {} as Config,
@ -41,7 +46,7 @@ export function useSettings() {
location: '',
handle: '',
clip: { w: 0, h: 0, x: 0, y: 0 },
crop: { x: 0, y: 0},
crop: { x: 0, y: 0 },
zoom: 1,
editImage: undefined,
})
@ -52,11 +57,11 @@ export function useSettings() {
}
const getSession = () => {
const session = app.state?.session;
const settings = session?.getSettings();
const identity = session?.getIdentity();
const session = app.state?.session
const settings = session?.getSettings()
const identity = session?.getIdentity()
if (!settings || !identity) {
console.log('session not set in settings hook');
console.log('session not set in settings hook')
}
return { settings, identity }
}
@ -64,24 +69,44 @@ export function useSettings() {
useEffect(() => {
const { settings, identity } = getSession()
const setConfig = (config: Config) => {
updateState({ config })
updateState({ config })
}
settings.addConfigListener(setConfig);
settings.addConfigListener(setConfig)
const setProfile = (profile: Profile) => {
const { handle, name, location, description } = profile;
const url = identity.getProfileImageUrl();
updateState({ profile, handle, name, location, description, imageUrl: url, editImage: url, profileSet: true })
const { handle, name, location, description } = profile
const url = identity.getProfileImageUrl()
updateState({
profile,
handle,
name,
location,
description,
imageUrl: url,
editImage: url,
profileSet: true,
})
}
identity.addProfileListener(setProfile)
return () => {
settings.removeConfigListener(setConfig);
identity.removeProfileListener(setProfile);
return () => {
settings.removeConfigListener(setConfig)
identity.removeProfileListener(setProfile)
}
}, []);
}, [])
useEffect(() => {
const { strings, dateFormat, timeFormat, themes, scheme, languages, language, audioId, audioInputs, videoId, videoInputs } =
display.state
const {
strings,
dateFormat,
timeFormat,
themes,
scheme,
languages,
language,
audioId,
audioInputs,
videoId,
videoInputs,
} = display.state
updateState({
strings,
dateFormat,
@ -99,68 +124,72 @@ export function useSettings() {
const actions = {
getUsernameStatus: async (username: string) => {
const { settings } = getSession();
return await settings.getUsernameStatus(username);
const { settings } = getSession()
return await settings.getUsernameStatus(username)
},
setLogin: async () => {
const { settings } = getSession();
await settings.setLogin(state.handle, state.password);
const { settings } = getSession()
await settings.setLogin(state.handle, state.password)
},
enableNotifications: async () => {
const { settings } = getSession();
await settings.enableNotifications();
const { settings } = getSession()
await settings.enableNotifications()
},
disableNotifications: async () => {
const { settings } = getSession();
await settings.disableNotifications();
const { settings } = getSession()
await settings.disableNotifications()
},
enableRegistry: async () => {
const { settings } = getSession();
await settings.enableRegistry();
const { settings } = getSession()
await settings.enableRegistry()
},
disableRegistry: async () => {
const { settings } = getSession();
await settings.disableRegistry();
const { settings } = getSession()
await settings.disableRegistry()
},
enableMFA: async () => {
const { settings } = getSession();
return await settings.enableMFA();
const { settings } = getSession()
return await settings.enableMFA()
},
disableMFA: async () => {
const { settings } = getSession();
await settings.disableMFA();
const { settings } = getSession()
await settings.disableMFA()
},
confirmMFA: async (code: string) => {
const { settings } = getSession();
await settings.confirmMFA(code);
const { settings } = getSession()
await settings.confirmMFA(code)
},
setSeal: async (password: string) => {
const { settings } = getSession();
await settings.setSeal(password);
const { settings } = getSession()
await settings.setSeal(password)
},
clearSeal: async () => {
const { settings } = getSession();
await settings.clearSeal();
const { settings } = getSession()
await settings.clearSeal()
},
unlockSeal: async (password: string) => {
const { settings } = getSession();
await settings.unlockSeal(password);
const { settings } = getSession()
await settings.unlockSeal(password)
},
forgetSeal: async () => {
const { settings } = getSession();
await settings.forgetSeal();
const { settings } = getSession()
await settings.forgetSeal()
},
setProfileData: async (name: string, location: string, description: string) => {
const { identity } = getSession();
await identity.setProfileData(name, location, description);
setProfileData: async (
name: string,
location: string,
description: string
) => {
const { identity } = getSession()
await identity.setProfileData(name, location, description)
},
setProfileImage: async (image: string) => {
const { identity } = getSession();
await identity.setProfileImage(image);
const { identity } = getSession()
await identity.setProfileImage(image)
},
getProfileImageUrl: () => {
const { identity } = getSession();
return identity.getProfileImageUrl();
const { identity } = getSession()
return identity.getProfileImageUrl()
},
setLanguage: (code: string) => {
display.actions.setLanguage(code)
@ -169,103 +198,110 @@ export function useSettings() {
display.actions.setTheme(theme)
},
setVideo: (device: string | null) => {
display.actions.setVideoInput(device ? device : null);
display.actions.setVideoInput(device ? device : null)
},
setAudio: (device: string | null) => {
display.actions.setAudioInput(device ? device : null);
display.actions.setAudioInput(device ? device : null)
},
setDateFormat: (format: string) => {
display.actions.setDateFormat(format);
display.actions.setDateFormat(format)
},
setTimeFormat: (format: string) => {
display.actions.setTimeFormat(format);
display.actions.setTimeFormat(format)
},
setAll: (all: boolean) => {
updateState({ all });
updateState({ all })
},
logout: async () => {
await app.actions.accountLogout(state.all);
await app.actions.accountLogout(state.all)
},
setHandle: (handle: string) => {
updateState({ handle, taken: false, checked: false });
clearTimeout(debounce.current);
updateState({ handle, taken: false, checked: false })
clearTimeout(debounce.current)
if (!handle || handle === state.profile.handle) {
updateState({ available: true, checked: true});
}
else {
updateState({ available: true, checked: true })
} else {
debounce.current = setTimeout(async () => {
const { settings } = getSession();
const available = await settings.getUsernameStatus(handle);
updateState({ taken: !available, checked: true });
}, DEBOUNCE_MS);
const { settings } = getSession()
const available = await settings.getUsernameStatus(handle)
updateState({ taken: !available, checked: true })
}, DEBOUNCE_MS)
}
},
setPassword: (password: string) => {
updateState({ password });
updateState({ password })
},
setConfirm: (confirm: string) => {
updateState({ confirm });
updateState({ confirm })
},
setName: (name: string) => {
updateState({ name });
updateState({ name })
},
setLocation: (location: string) => {
updateState({ location });
updateState({ location })
},
setDescription: (description: string) => {
updateState({ description });
updateState({ description })
},
setDetails: async () => {
const { identity } = getSession();
const { name, location, description } = state;
await identity.setProfileData(name, location, description);
const { identity } = getSession()
const { name, location, description } = state
await identity.setProfileData(name, location, description)
},
setCrop: (crop: Point) => {
updateState({ crop });
updateState({ crop })
},
setZoom: (zoom: number) => {
updateState({ zoom });
updateState({ zoom })
},
setEditImageCrop: (clip: Area) => {
updateState({ clip });
updateState({ clip })
},
setEditImage: (editImage: string) => {
updateState({ editImage });
updateState({ editImage })
},
setImage: async () => {
const { identity } = getSession();
const { identity } = getSession()
const processImg = () => {
return new Promise<string>((resolve, reject) => {
let img = new Image();
let img = new Image()
img.onload = () => {
try {
const canvas = document.createElement("canvas");
const context = canvas.getContext('2d');
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
if (!context) {
throw new Error('failed to allocate context');
throw new Error('failed to allocate context')
}
canvas.width = IMAGE_DIM;
canvas.height = IMAGE_DIM;
context.imageSmoothingQuality = "medium";
context.drawImage(img, state.clip.x, state.clip.y, state.clip.width, state.clip.height,
0, 0, IMAGE_DIM, IMAGE_DIM);
resolve(canvas.toDataURL());
}
catch (err) {
console.log(err);
reject();
canvas.width = IMAGE_DIM
canvas.height = IMAGE_DIM
context.imageSmoothingQuality = 'medium'
context.drawImage(
img,
state.clip.x,
state.clip.y,
state.clip.width,
state.clip.height,
0,
0,
IMAGE_DIM,
IMAGE_DIM
)
resolve(canvas.toDataURL())
} catch (err) {
console.log(err)
reject()
}
}
if (!state.editImage) {
throw new Error('invalid edit image');
throw new Error('invalid edit image')
}
img.onerror = reject;
img.src = state.editImage;
});
};
const dataUrl = await processImg();
const data = dataUrl.split(",")[1];
await identity.setProfileImage(data);
img.onerror = reject
img.src = state.editImage
})
}
const dataUrl = await processImg()
const data = dataUrl.split(',')[1]
await identity.setProfileImage(data)
},
}