Webclient: Handle firing an event once (#4499)

* draft: handle firing an event once

* lint

* Prevent rapid double-click on sending messages

* no rest spread on single primative when sibling components exist

* clear message instead of using a fireOnce handler.

* fix tests

* remove unnecessary validate mock
This commit is contained in:
Brent Clark 2022-01-30 11:14:28 -06:00 committed by GitHub
parent 4bb13677c8
commit 513fcb0908
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 21467 additions and 161 deletions

View file

@ -1,19 +1,16 @@
// eslint-disable-next-line
import React from "react";
import { connect } from 'react-redux';
import { Form, reduxForm } from 'redux-form'
import React from 'react';
import { Form } from 'react-final-form'
import { InputAction } from 'components';
import { FormKey } from 'types';
const AddToBuddies = ({ handleSubmit }) => (
<Form onSubmit={handleSubmit}>
<InputAction action="Add" label="Add to Buddies" name="userName" />
const AddToBuddies = ({ onSubmit }) => (
<Form onSubmit={values => onSubmit(values)}>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<InputAction action="Add" label="Add to Buddies" name="userName" />
</form>
)}
</Form>
);
const propsMap = {
form: FormKey.ADD_TO_BUDDIES
};
export default connect()(reduxForm(propsMap)(AddToBuddies));
export default AddToBuddies;

View file

@ -1,19 +1,16 @@
// eslint-disable-next-line
import React from "react";
import { connect } from 'react-redux';
import { Form, reduxForm } from 'redux-form'
import React from 'react';
import { Form } from 'react-final-form'
import { InputAction } from 'components';
import { FormKey } from 'types';
const AddToIgnore = ({ handleSubmit }) => (
<Form onSubmit={handleSubmit}>
<InputAction action="Add" label="Add to Ignore" name="userName" />
const AddToIgnore = ({ onSubmit }) => (
<Form onSubmit={values => onSubmit(values)}>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<InputAction action="Add" label="Add to Ignore" name="userName" />
</form>
)}
</Form>
);
const propsMap = {
form: FormKey.ADD_TO_IGNORE,
};
export default connect()(reduxForm(propsMap)(AddToIgnore));
export default AddToIgnore;

View file

@ -11,7 +11,7 @@ import Typography from '@material-ui/core/Typography';
import { AuthenticationService } from 'api';
import { RegistrationDialog, RequestPasswordResetDialog, ResetPasswordDialog } from 'dialogs';
import { LoginForm } from 'forms';
import { useReduxEffect } from 'hooks';
import { useReduxEffect, useFireOnce } from 'hooks';
import { Images } from 'images';
import { HostDTO } from 'services';
import { RouteEnum, WebSocketConnectOptions, getHostPort } from 'types';
@ -77,15 +77,6 @@ const Login = ({ state, description }: LoginProps) => {
closeResetPasswordDialog();
}, ServerTypes.RESET_PASSWORD_SUCCESS, []);
useReduxEffect(({ options: { hashedPassword } }) => {
if (hostIdToRemember) {
HostDTO.get(hostIdToRemember).then(host => {
host.hashedPassword = hashedPassword;
host.save();
});
}
}, ServerTypes.LOGIN_SUCCESSFUL, [hostIdToRemember]);
const showDescription = () => {
return !isConnected && description?.length;
};
@ -121,6 +112,19 @@ const Login = ({ state, description }: LoginProps) => {
AuthenticationService.login(options as WebSocketConnectOptions);
}, []);
const [submitButtonDisabled, resetSubmitButton, handleLogin] = useFireOnce(onSubmitLogin)
useReduxEffect(({ options: { hashedPassword } }) => {
if (hostIdToRemember) {
HostDTO.get(hostIdToRemember).then(host => {
host.hashedPassword = hashedPassword;
host.save();
});
}
resetSubmitButton()
}, ServerTypes.LOGIN_SUCCESSFUL, [hostIdToRemember]);
const updateHost = ({ selectedHost, userName, hashedPassword, remember }) => {
HostDTO.get(selectedHost.id).then(hostDTO => {
hostDTO.remember = remember;
@ -208,7 +212,11 @@ const Login = ({ state, description }: LoginProps) => {
<Typography variant="h1">Login</Typography>
<Typography variant="subtitle1">A cross-platform virtual tabletop for multiplayer card games.</Typography>
<div className="login-form">
<LoginForm onSubmit={onSubmitLogin} onResetPassword={openRequestPasswordResetDialog} />
<LoginForm
onSubmit={handleLogin}
onResetPassword={openRequestPasswordResetDialog}
disableSubmitButton={submitButtonDisabled}
/>
</div>
{

View file

@ -1,5 +1,4 @@
// eslint-disable-next-line
import React, { Component } from "react";
import React from 'react';
import { connect } from 'react-redux';
import { withRouter, generatePath } from 'react-router-dom';
@ -8,7 +7,7 @@ import Paper from '@material-ui/core/Paper';
import { RoomsService } from 'api';
import { ScrollToBottomOnChanges, ThreePaneLayout, UserDisplay, VirtualList, AuthGuard } from 'components';
import { RoomsStateMessages, RoomsStateRooms, JoinedRooms, RoomsSelectors } from 'store';
import { RoomsStateMessages, RoomsStateRooms, JoinedRooms, RoomsSelectors, RoomsTypes } from 'store';
import { RouteEnum } from 'types';
import OpenGames from './OpenGames';
@ -18,86 +17,73 @@ import SayMessage from './SayMessage';
import './Room.css';
// @TODO (3)
class Room extends Component<any> {
componentDidUpdate() {
const { joined, match, history } = this.props;
let { roomId } = match.params;
function Room(props) {
roomId = parseInt(roomId, 0);
const { joined, match, history, rooms, messages } = props;
const roomId = parseInt(match.params.roomId, 0);
if (!joined.find(({ roomId: id }) => id === roomId)) {
history.push(generatePath(RouteEnum.SERVER));
}
if (!joined.find(({ roomId: id }) => id === roomId)) {
history.push(generatePath(RouteEnum.SERVER));
}
constructor(props) {
super(props);
this.handleRoomSay = this.handleRoomSay.bind(this);
}
handleRoomSay({ message }) {
function handleRoomSay({ message }) {
if (message) {
const { roomId } = this.props.match.params;
RoomsService.roomSay(roomId, message);
}
}
render() {
const { match, rooms } = this.props;
const { roomId } = match.params;
const room = rooms[roomId];
const room = rooms[roomId];
const messages = this.props.messages[roomId];
const users = room.userList;
const roomMessages = messages[roomId];
const users = room.userList;
return (
<div className="room-view">
<AuthGuard />
return (
<div className="room-view">
<AuthGuard />
<div className="room-view__main">
<ThreePaneLayout
fixedHeight
<div className="room-view__main">
<ThreePaneLayout
fixedHeight
top={(
<Paper className="room-view__games overflow-scroll">
<OpenGames room={room} />
top={(
<Paper className="room-view__games overflow-scroll">
<OpenGames room={room} />
</Paper>
)}
bottom={(
<div className="room-view__messages">
<Paper className="room-view__messages-content overflow-scroll">
<ScrollToBottomOnChanges changes={roomMessages} content={(
<Messages messages={roomMessages} />
)} />
</Paper>
)}
<Paper className="room-view__messages-sayMessage">
<SayMessage onSubmit={handleRoomSay} />
</Paper>
</div>
)}
bottom={(
<div className="room-view__messages">
<Paper className="room-view__messages-content overflow-scroll">
<ScrollToBottomOnChanges changes={messages} content={(
<Messages messages={messages} />
)} />
</Paper>
<Paper className="room-view__messages-sayMessage">
<SayMessage onSubmit={this.handleRoomSay} />
</Paper>
</div>
)}
side={(
<Paper className="room-view__side overflow-scroll">
<div className="room-view__side-label">
side={(
<Paper className="room-view__side overflow-scroll">
<div className="room-view__side-label">
Users in this room: {users.length}
</div>
<VirtualList
className="room-view__side-list"
itemKey={(index, data) => users[index].name }
items={ users.map(user => (
<ListItem button className="room-view__side-list__item">
<UserDisplay user={user} />
</ListItem>
)) }
/>
</Paper>
)}
/>
</div>
</div>
<VirtualList
className="room-view__side-list"
itemKey={(index, data) => users[index].name }
items={ users.map(user => (
<ListItem button className="room-view__side-list__item">
<UserDisplay user={user} />
</ListItem>
)) }
/>
</Paper>
)}
/>
</div>
);
}
</div>
);
}
interface RoomProps {

View file

@ -1,18 +1,24 @@
// eslint-disable-next-line
import React from "react";
import { connect } from 'react-redux';
import { Form, reduxForm } from 'redux-form'
import React from 'react';
import { Form } from 'react-final-form'
import { InputAction } from 'components';
const SayMessage = ({ handleSubmit }) => (
<Form onSubmit={handleSubmit}>
<InputAction action="Send" label="Chat" name="message" />
</Form>
);
const required = (value) => (value ? undefined : 'Required');
const propsMap = {
form: 'sayMessage'
};
const SayMessage = (props) => {
const { onSubmit } = props
return (
<Form onSubmit={values => onSubmit(values)}>
{({ handleSubmit, form }) => (
<form onSubmit={e => {
handleSubmit(e)
form.restart()
}}>
<InputAction action="Send" label="Chat" name="message" validate={required}/>
</form>
)}
</Form>
);
}
export default connect()(reduxForm(propsMap)(SayMessage));
export default SayMessage;