Webatrice: KnownHosts component (#4456)

* refactor dexie services for future schema updates

Co-authored-by: Jeremy Letto <jeremy.letto@datasite.com>
This commit is contained in:
Jeremy Letto 2021-11-25 21:12:23 -06:00 committed by GitHub
parent 37879c4255
commit 6ce346af4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 1381 additions and 1291 deletions

View file

@ -1,5 +1,6 @@
.KnownHosts {
width: 100%;
position: relative;
}
.KnownHosts-item__label {
@ -15,6 +16,27 @@
font-size: .9em;
}
.KnownHosts-validation {
position: absolute;
top: 0;
right: 0;
transform: translateY(-100%);
font-weight: bold;
}
.KnownHosts-error {
display: flex;
align-items: center;
}
.KnownHosts-error svg {
margin-left: 4px;
}
.Mui-selected .KnownHosts-item__label svg {
display: block;
}
.MuiSelect-selectMenu .KnownHosts-item__edit {
display: none;
}

View file

@ -1,85 +1,209 @@
// eslint-disable-next-line
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { Select, MenuItem } from '@material-ui/core';
import Button from '@material-ui/core/Button';
import FormControl from '@material-ui/core/FormControl';
import IconButton from '@material-ui/core/IconButton';
import InputLabel from '@material-ui/core/InputLabel';
import { makeStyles } from '@material-ui/core/styles';
import Check from '@material-ui/icons/Check';
import AddIcon from '@material-ui/icons/Add';
import EditRoundedIcon from '@material-ui/icons/Edit';
import ErrorOutlinedIcon from '@material-ui/icons/ErrorOutlined';
import { KnownHostDialog } from 'dialogs';
import { HostDTO } from 'services';
import { DefaultHosts, getHostPort } from 'types';
import { DefaultHosts, Host, getHostPort } from 'types';
import './KnownHosts.css';
const KnownHosts = ({ onChange }) => {
const [state, setState] = useState({
const useStyles = makeStyles(theme => ({
root: {
'& .KnownHosts-error': {
color: theme.palette.error.main
},
'& .KnownHosts-warning': {
color: theme.palette.warning.main
}
},
}));
const KnownHosts = ({ input: { onChange }, meta: { touched, error, warning } }) => {
const classes = useStyles();
const [hostsState, setHostsState] = useState({
hosts: [],
selectedHost: 0,
selectedHost: {} as any,
});
useEffect(() => {
HostDTO.getAll().then(async hosts => {
if (hosts?.length) {
setState(s => ({ ...s, hosts }));
} else {
setState(s => ({ ...s, hosts: DefaultHosts }));
await HostDTO.bulkAdd(DefaultHosts);
}
});
const [dialogState, setDialogState] = useState({
open: false,
edit: null,
});
const loadKnownHosts = useCallback(async () => {
const hosts = await HostDTO.getAll();
if (!hosts?.length) {
// @TODO: find a better pattern to seeding default data in indexedDB
await HostDTO.bulkAdd(DefaultHosts);
loadKnownHosts();
} else {
const selectedHost = hosts.find(({ lastSelected }) => lastSelected) || hosts[0];
setHostsState(s => ({ ...s, hosts, selectedHost }));
}
}, []);
useEffect(() => {
if (state.hosts.length) {
onChange(getHostPort(state.hosts[state.selectedHost]));
loadKnownHosts();
}, [loadKnownHosts]);
useEffect(() => {
const { hosts, selectedHost } = hostsState;
if (selectedHost?.id) {
updateLastSelectedHost(selectedHost.id).then(() => {
onChange(selectedHost);
});
}
}, [state, onChange]);
}, [hostsState, onChange]);
const selectHost = (selectedHost) => {
setState(s => ({ ...s, selectedHost }));
setHostsState(s => ({ ...s, selectedHost }));
};
const addKnownHost = () => {
console.log('KnownHosts->addKnownHost');
const openAddKnownHostDialog = () => {
setDialogState(s => ({ ...s, open: true, edit: null }));
};
const editKnownHost = (hostIndex) => {
console.log('KnownHosts->editKnownHost: ', state.hosts[hostIndex]);
const openEditKnownHostDialog = (host: HostDTO) => {
setDialogState(s => ({ ...s, open: true, edit: host }));
};
const closeKnownHostDialog = () => {
setDialogState(s => ({ ...s, open: false }));
}
const handleDialogRemove = async ({ id }) => {
setHostsState(s => ({
...s,
hosts: s.hosts.filter(host => host.id !== id),
selectedHost: s.selectedHost.id === id ? s.hosts[0] : s.selectedHost,
}));
closeKnownHostDialog();
HostDTO.delete(id);
};
const handleDialogSubmit = async ({ id, name, host, port }) => {
if (id) {
const hostDTO = await HostDTO.get(id);
hostDTO.name = name;
hostDTO.host = host;
hostDTO.port = port;
await hostDTO.save();
setHostsState(s => ({
...s,
hosts: s.hosts.map(h => h.id === id ? hostDTO : h),
selectedHost: hostDTO
}));
} else {
const newHost: Host = { name, host, port, editable: true };
newHost.id = await HostDTO.add(newHost) as number;
setHostsState(s => ({
...s,
hosts: [...s.hosts, newHost],
selectedHost: newHost,
}));
}
closeKnownHostDialog();
};
const updateLastSelectedHost = (hostId): Promise<any[]> => {
return HostDTO.getAll().then(hosts =>
hosts.map(async host => {
if (host.id === hostId) {
host.lastSelected = true;
return await host.save();
}
if (host.lastSelected) {
host.lastSelected = false;
return await host.save();
}
return host;
})
);
};
return (
<FormControl variant='outlined' className='KnownHosts'>
<InputLabel id='KnownHosts-select'>Host</InputLabel>
<Select
id='KnownHosts-select'
labelId='KnownHosts-label'
label='Host'
margin='dense'
value={state.selectedHost}
fullWidth={true}
onChange={e => selectHost(e.target.value)}
>
<Button value={state.selectedHost} onClick={addKnownHost}>Add</Button>
<div>
<FormControl variant='outlined' className={'KnownHosts ' + classes.root}>
{ touched && (
<div className="KnownHosts-validation">
{
(error &&
<div className="KnownHosts-error">
{error}
<ErrorOutlinedIcon style={{ fontSize: 'small', fontWeight: 'bold' }} />
</div>
) ||
{
state.hosts.map((host, index) => (
<MenuItem className='KnownHosts-item' value={index} key={index}>
<div className='KnownHosts-item__label'>
<Check />
<span>{host.name} ({ getHostPort(state.hosts[index]).host }:{getHostPort(state.hosts[index]).port})</span>
</div>
(warning && <div className="KnownHosts-warning">{warning}</div>)
}
</div>
) }
{ host.editable && (
<IconButton size='small' color='primary' onClick={() => editKnownHost(index)}>
<EditRoundedIcon fontSize='small' />
</IconButton>
) }
</MenuItem>
))
}
</Select>
</FormControl>
<InputLabel id='KnownHosts-select'>Host</InputLabel>
<Select
id='KnownHosts-select'
labelId='KnownHosts-label'
label='Host'
margin='dense'
name='host'
value={hostsState.selectedHost}
fullWidth={true}
onChange={e => selectHost(e.target.value)}
>
<Button value={hostsState.selectedHost} onClick={openAddKnownHostDialog}>
<span>Add new host</span>
<AddIcon fontSize='small' color='primary' />
</Button>
{
hostsState.hosts.map((host, index) => (
<MenuItem className='KnownHosts-item' value={host} key={index}>
<div className='KnownHosts-item__label'>
<Check />
<span>{host.name} ({ getHostPort(hostsState.hosts[index]).host }:{getHostPort(hostsState.hosts[index]).port})</span>
</div>
{ host.editable && (
<IconButton className='KnownHosts-item__edit' size='small' color='primary' onClick={(e) => {
openEditKnownHostDialog(hostsState.hosts[index]);
}}>
<EditRoundedIcon fontSize='small' />
</IconButton>
) }
</MenuItem>
))
}
</Select>
</FormControl>
<KnownHostDialog
isOpen={dialogState.open}
host={dialogState.edit}
onRemove={handleDialogRemove}
onSubmit={handleDialogSubmit}
handleClose={closeKnownHostDialog}
/>
</div>
)
};