mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-04-27 07:48:01 -07:00
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:
parent
37879c4255
commit
6ce346af4a
54 changed files with 1381 additions and 1291 deletions
|
|
@ -1,5 +0,0 @@
|
|||
.dialog-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
import React from 'react';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogContent from '@material-ui/core/DialogContent';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
import { CardImportForm } from 'forms';
|
||||
|
||||
import './CardImportDialog.css';
|
||||
|
||||
const CardImportDialog = ({ classes, handleClose, isOpen }: any) => {
|
||||
const handleOnClose = () => {
|
||||
handleClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog onClose={handleOnClose} open={isOpen}>
|
||||
<DialogTitle disableTypography className="dialog-title">
|
||||
<Typography variant="h6">Import Cards</Typography>
|
||||
|
||||
{handleOnClose ? (
|
||||
<IconButton onClick={handleOnClose}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
) : null}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<CardImportForm onSubmit={handleOnClose}></CardImportForm>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardImportDialog;
|
||||
|
|
@ -13,14 +13,13 @@ import MenuRoundedIcon from '@material-ui/icons/MenuRounded';
|
|||
import * as _ from 'lodash';
|
||||
|
||||
import { AuthenticationService, RoomsService } from 'api';
|
||||
import { CardImportDialog } from 'dialogs';
|
||||
import { Images } from 'images';
|
||||
import { RoomsSelectors, ServerSelectors } from 'store';
|
||||
import { Room, RouteEnum, User } from 'types';
|
||||
|
||||
import './Header.css';
|
||||
|
||||
import CardImportDialog from '../CardImportDialog/CardImportDialog';
|
||||
|
||||
class Header extends Component<HeaderProps> {
|
||||
state: HeaderState;
|
||||
options: string[] = [
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
.inputField {
|
||||
.InputField {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.inputField-validation {
|
||||
.InputField-validation {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
|
@ -10,11 +10,11 @@
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.inputField-error {
|
||||
.InputField-error {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.inputField-error svg {
|
||||
.InputField-error svg {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,47 +1,55 @@
|
|||
import React from 'react';
|
||||
import { styled } from '@material-ui/core/styles';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import ErrorOutlinedIcon from '@material-ui/icons/ErrorOutlined';
|
||||
|
||||
import './InputField.css';
|
||||
|
||||
const InputField = ({ input, label, name, autoComplete, type, meta: { touched, error, warning } }) => (
|
||||
<div className="inputField">
|
||||
{ touched && (
|
||||
<div className="inputField-validation">
|
||||
{
|
||||
(error &&
|
||||
<ThemedFieldError className="inputField-error">
|
||||
{error}
|
||||
<ErrorOutlinedIcon style={{ fontSize: 'small', fontWeight: 'bold' }} />
|
||||
</ThemedFieldError>
|
||||
) ||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
'& .InputField-error': {
|
||||
color: theme.palette.error.main
|
||||
},
|
||||
|
||||
(warning && <ThemedFieldWarning className="inputField-warning">{warning}</ThemedFieldWarning>)
|
||||
}
|
||||
</div>
|
||||
) }
|
||||
|
||||
<TextField
|
||||
className="rounded"
|
||||
variant="outlined"
|
||||
margin="dense"
|
||||
fullWidth={true}
|
||||
label={label}
|
||||
name={name}
|
||||
type={type}
|
||||
autoComplete={autoComplete}
|
||||
{ ...input }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ThemedFieldError = styled('div')(({ theme }) => ({
|
||||
color: theme.palette.error.main
|
||||
'& .InputField-warning': {
|
||||
color: theme.palette.warning.main
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
const ThemedFieldWarning = styled('div')(({ theme }) => ({
|
||||
color: theme.palette.warning.main
|
||||
}));
|
||||
const InputField = ({ input, label, name, autoComplete, type, meta: { touched, error, warning } }) => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<div className={'InputField ' + classes.root}>
|
||||
{ touched && (
|
||||
<div className="InputField-validation">
|
||||
{
|
||||
(error &&
|
||||
<div className="InputField-error">
|
||||
{error}
|
||||
<ErrorOutlinedIcon style={{ fontSize: 'small', fontWeight: 'bold' }} />
|
||||
</div>
|
||||
) ||
|
||||
|
||||
(warning && <div className="InputField-warning">{warning}</div>)
|
||||
}
|
||||
</div>
|
||||
) }
|
||||
|
||||
<TextField
|
||||
className="rounded"
|
||||
variant="outlined"
|
||||
margin="dense"
|
||||
fullWidth={true}
|
||||
label={label}
|
||||
name={name}
|
||||
type={type}
|
||||
autoComplete={autoComplete}
|
||||
{ ...input }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputField;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
.dialog-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
import React from 'react';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogContent from '@material-ui/core/DialogContent';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
import { RegisterForm } from 'forms';
|
||||
|
||||
import './RegistrationDialog.css';
|
||||
|
||||
const RegistrationDialog = ({ classes, handleClose, isOpen }: any) => {
|
||||
const handleOnClose = () => {
|
||||
handleClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog onClose={handleOnClose} open={isOpen}>
|
||||
<DialogTitle disableTypography className="dialog-title">
|
||||
<Typography variant="h6">Create New Account</Typography>
|
||||
|
||||
{handleOnClose ? (
|
||||
<IconButton onClick={handleOnClose}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
) : null}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<RegisterForm onSubmit={handleOnClose}></RegisterForm>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegistrationDialog;
|
||||
|
|
@ -12,13 +12,11 @@ export { default as ThreePaneLayout } from './ThreePaneLayout/ThreePaneLayout';
|
|||
export { default as CheckboxField } from './CheckboxField/CheckboxField';
|
||||
export { default as SelectField } from './SelectField/SelectField';
|
||||
export { default as ScrollToBottomOnChanges } from './ScrollToBottomOnChanges/ScrollToBottomOnChanges';
|
||||
export { default as RegistrationDialog } from './RegistrationDialog/RegistrationDialog';
|
||||
|
||||
// Guards
|
||||
export { default as AuthGuard } from './Guard/AuthGuard';
|
||||
export { default as ModGuard } from './Guard/ModGuard';
|
||||
|
||||
// Dialogs
|
||||
export { default as CardImportDialog } from './CardImportDialog/CardImportDialog';
|
||||
export { default as RequestPasswordResetDialog } from './RequestPasswordResetDialog/RequestPasswordResetDialog';
|
||||
export { default as ResetPasswordDialog } from './ResetPasswordDialog/ResetPasswordDialog';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue