Webatrice updates (#4366)

This commit is contained in:
Jeremy Letto 2021-05-21 20:23:30 -05:00 committed by GitHub
parent 8db9475804
commit 0d05f9097d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 5517 additions and 20090 deletions

25253
webclient/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -3,11 +3,12 @@
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@material-ui/core": "^4.6.1", "@material-ui/core": "^4.11.4",
"@material-ui/icons": "^4.11.2",
"@types/jest": "24.0.20", "@types/jest": "24.0.20",
"@types/jquery": "^3.3.31", "@types/jquery": "^3.3.31",
"@types/lodash": "^4.14.145", "@types/lodash": "^4.14.145",
"@types/material-ui": "^0.21.7", "@types/material-ui": "^0.21.8",
"@types/node": "12.11.7", "@types/node": "12.11.7",
"@types/prop-types": "^15.7.3", "@types/prop-types": "^15.7.3",
"@types/protobufjs": "^6.0.0", "@types/protobufjs": "^6.0.0",

View file

@ -14,7 +14,9 @@ export default class AuthenticationService {
} }
static isModerator(user) { static isModerator(user) {
return user.userLevel >= webClient.pb.ServerInfo_User.UserLevelFlag.IsModerator; const moderatorLevel = webClient.pb.ServerInfo_User.UserLevelFlag.IsModerator;
// @TODO tell cockatrice not to do this so shittily
return (user.userLevel & moderatorLevel) === moderatorLevel;
} }
static isAdmin() { static isAdmin() {

View file

@ -1,4 +1,4 @@
import React, { Component } from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Redirect } from "react-router-dom"; import { Redirect } from "react-router-dom";
@ -7,12 +7,10 @@ import { RouteEnum } from "types";
import { AuthenticationService } from "api"; import { AuthenticationService } from "api";
class AuthGuard extends Component<AuthGuardProps> { const AuthGuard = ({ state }: AuthGuardProps) => {
render() { return !AuthenticationService.isConnected(state)
return !AuthenticationService.isConnected(this.props.state) ? <Redirect from="*" to={RouteEnum.SERVER} />
? <Redirect from="*" to={RouteEnum.SERVER} /> : <div></div>;
: "";
}
}; };
interface AuthGuardProps { interface AuthGuardProps {
@ -23,4 +21,4 @@ const mapStateToProps = state => ({
state: ServerSelectors.getState(state), state: ServerSelectors.getState(state),
}); });
export default connect(mapStateToProps)(AuthGuard); export default connect(mapStateToProps)(AuthGuard);

View file

@ -25,13 +25,13 @@
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
margin-right: 10px;
} }
.Header-nav__items { .Header-nav__items {
width: 100%; width: 100%;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
align-items: center;
} }
.Header-nav__item { .Header-nav__item {
@ -39,9 +39,13 @@
margin: 0 10px; margin: 0 10px;
} }
.Header-nav__menu {
margin-left: 10px;
}
.Header-account { .Header-account {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.Header-account__name { .Header-account__name {

View file

@ -3,7 +3,11 @@ import { connect } from "react-redux";
import { NavLink, withRouter, generatePath } from "react-router-dom"; import { NavLink, withRouter, generatePath } from "react-router-dom";
import AppBar from "@material-ui/core/AppBar"; import AppBar from "@material-ui/core/AppBar";
import Chip from "@material-ui/core/Chip"; import Chip from "@material-ui/core/Chip";
import IconButton from "@material-ui/core/IconButton";
import Menu from "@material-ui/core/Menu";
import MenuItem from "@material-ui/core/MenuItem";
import Toolbar from "@material-ui/core/Toolbar"; import Toolbar from "@material-ui/core/Toolbar";
import MenuRoundedIcon from '@material-ui/icons/MenuRounded';
import * as _ from "lodash"; import * as _ from "lodash";
import { AuthenticationService, RoomsService } from "api"; import { AuthenticationService, RoomsService } from "api";
@ -14,6 +18,24 @@ import "./Header.css";
import logo from "./logo.png"; import logo from "./logo.png";
class Header extends Component<HeaderProps> { class Header extends Component<HeaderProps> {
state: HeaderState;
options: string[] = [
'Decks',
'Replays',
];
constructor(props) {
super(props);
this.state = {
anchorEl: null,
};
this.handleMenuClick = this.handleMenuClick.bind(this);
this.handleMenuItemClick = this.handleMenuItemClick.bind(this);
this.handleMenuClose = this.handleMenuClose.bind(this);
}
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const currentRooms = this.props.joinedRooms; const currentRooms = this.props.joinedRooms;
const previousRooms = prevProps.joinedRooms; const previousRooms = prevProps.joinedRooms;
@ -24,8 +46,30 @@ class Header extends Component<HeaderProps> {
} }
} }
handleMenuClick({ target }) {
this.setState({ anchorEl: target });
}
handleMenuItemClick(option: string) {
const route = RouteEnum[option.toUpperCase()];
this.props.history.push(generatePath(route));
this.handleMenuClose();
}
handleMenuClose() {
this.setState({ anchorEl: null });
}
render() { render() {
const { joinedRooms, server, state, user } = this.props; const { joinedRooms, server, state, user } = this.props;
const anchorEl = this.state.anchorEl;
const options = [ ...this.options ];
if (user && AuthenticationService.isModerator(user)) {
options.push('Administration');
options.push('Logs');
}
return ( return (
<div> <div>
@ -39,20 +83,6 @@ class Header extends Component<HeaderProps> {
<div className="Header-content"> <div className="Header-content">
<nav className="Header-nav"> <nav className="Header-nav">
<ul className="Header-nav__items"> <ul className="Header-nav__items">
{
AuthenticationService.isModerator(user) && (
<li className="Header-nav__item">
<NavLink to={RouteEnum.LOGS}>
<button>Logs</button>
</NavLink>
</li>
)
}
<li className="Header-nav__item">
<NavLink to={RouteEnum.SERVER} className="plain-link">
Server ({server})
</NavLink>
</li>
<NavLink to={RouteEnum.ACCOUNT} className="plain-link"> <NavLink to={RouteEnum.ACCOUNT} className="plain-link">
<div className="Header-account"> <div className="Header-account">
<span className="Header-account__name"> <span className="Header-account__name">
@ -61,6 +91,35 @@ class Header extends Component<HeaderProps> {
<span className="Header-account__indicator"></span> <span className="Header-account__indicator"></span>
</div> </div>
</NavLink> </NavLink>
<div className="Header-nav__menu">
<IconButton
aria-label="more"
aria-controls="long-menu"
aria-haspopup="true"
onClick={this.handleMenuClick}
>
<MenuRoundedIcon fontSize="large" />
</IconButton>
<Menu
id="long-menu"
anchorEl={anchorEl}
keepMounted
open={!!anchorEl}
onClose={this.handleMenuClose}
PaperProps={{
style: {
marginTop: '53px',
width: '20ch',
},
}}
>
{options.map((option) => (
<MenuItem key={option} onClick={() => this.handleMenuItemClick(option)}>
{option}
</MenuItem>
))}
</Menu>
</div>
</ul> </ul>
</nav> </nav>
</div> </div>
@ -113,11 +172,15 @@ interface HeaderProps {
history: any; history: any;
} }
interface HeaderState {
anchorEl: Element;
}
const mapStateToProps = state => ({ const mapStateToProps = state => ({
state: ServerSelectors.getState(state), state: ServerSelectors.getState(state),
server: ServerSelectors.getName(state), server: ServerSelectors.getName(state),
user: ServerSelectors.getUser(state), user: ServerSelectors.getUser(state),
joinedRooms: RoomsSelectors.getJoinedRooms(state) joinedRooms: RoomsSelectors.getJoinedRooms(state),
}); });
export default withRouter(connect(mapStateToProps)(Header)); export default withRouter(connect(mapStateToProps)(Header));

View file

@ -4,6 +4,7 @@ import { connect } from "react-redux";
import { Form, reduxForm } from "redux-form" import { Form, reduxForm } from "redux-form"
import { InputAction } from 'components'; import { InputAction } from 'components';
import { FormKey } from 'types';
const AddToBuddies = ({ handleSubmit }) => ( const AddToBuddies = ({ handleSubmit }) => (
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
@ -12,7 +13,7 @@ const AddToBuddies = ({ handleSubmit }) => (
); );
const propsMap = { const propsMap = {
form: "addToBuddies" form: FormKey.ADD_TO_BUDDIES
}; };
export default connect()(reduxForm(propsMap)(AddToBuddies)); export default connect()(reduxForm(propsMap)(AddToBuddies));

View file

@ -4,6 +4,7 @@ import { connect } from "react-redux";
import { Form, reduxForm } from "redux-form" import { Form, reduxForm } from "redux-form"
import { InputAction } from 'components'; import { InputAction } from 'components';
import { FormKey } from 'types';
const AddToIgnore = ({ handleSubmit }) => ( const AddToIgnore = ({ handleSubmit }) => (
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
@ -12,7 +13,7 @@ const AddToIgnore = ({ handleSubmit }) => (
); );
const propsMap = { const propsMap = {
form: "addToIgnore" form: FormKey.ADD_TO_IGNORE,
}; };
export default connect()(reduxForm(propsMap)(AddToIgnore)); export default connect()(reduxForm(propsMap)(AddToIgnore));

View file

@ -1,39 +1,37 @@
// eslint-disable-next-line // eslint-disable-next-line
import React, { Component } from "react"; import React, { Component } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Form, Field, InjectedFormProps, reduxForm } from "redux-form" import { Form, Field, reduxForm } from "redux-form"
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import { InputField } from "../../components";
import { InputField } from "components";
import { FormKey } from 'types';
import "./ConnectForm.css"; import "./ConnectForm.css";
class ConnectForm extends Component<InjectedFormProps> { const ConnectForm = ({ handleSubmit }) => (
render() { <Form className="connectForm" onSubmit={handleSubmit}>
return ( <div className="connectForm-item">
<Form className="connectForm" onSubmit={this.props.handleSubmit}> <Field label="Host" name="host" component={InputField} />
<div className="connectForm-item"> </div>
<Field label="Host" name="host" component={InputField} /> <div className="connectForm-item">
</div> <Field label="Port" name="port" component={InputField} />
<div className="connectForm-item"> </div>
<Field label="Port" name="port" component={InputField} /> <div className="connectForm-item">
</div> <Field label="User" name="user" component={InputField} autoComplete="username" />
<div className="connectForm-item"> </div>
<Field label="User" name="user" component={InputField} autoComplete="username" /> <div className="connectForm-item">
</div> <Field label="Pass" name="pass" type="password" component={InputField} autoComplete="current-password" />
<div className="connectForm-item"> </div>
<Field label="Pass" name="pass" type="password" component={InputField} autoComplete="current-password" /> <Button className="connectForm-submit" color="primary" variant="contained" type="submit">
</div> Connect
<Button className="connectForm-submit" color="primary" variant="contained" type="submit"> </Button>
Connect </Form>
</Button> );
</Form>
);
}
}
const propsMap = { const propsMap = {
form: "connect" form: FormKey.CONNECT
}; };
const mapStateToProps = () => ({ const mapStateToProps = () => ({

View file

@ -1,52 +1,49 @@
// eslint-disable-next-line // eslint-disable-next-line
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Form, Field, InjectedFormProps, reduxForm } from 'redux-form' import { Form, Field, reduxForm } from 'redux-form'
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
import { InputField } from '../../components'; import { InputField } from 'components';
import { FormKey } from 'types';
import './RegisterForm.css'; import './RegisterForm.css';
class RegisterForm extends Component<InjectedFormProps> { const RegisterForm = ({ handleSubmit }) => (
render() { <Form className="registerForm" onSubmit={handleSubmit} autoComplete="off">
return ( <div className="registerForm-item">
<Form className="registerForm" onSubmit={this.props.handleSubmit} autoComplete="off"> <Field label="Host" name="host" component={InputField} />
<div className="registerForm-item"> </div>
<Field label="Host" name="host" component={InputField} /> <div className="registerForm-item">
</div> <Field label="Port" name="port" component={InputField} />
<div className="registerForm-item"> </div>
<Field label="Port" name="port" component={InputField} /> <div className="registerForm-item">
</div> <Field label="Player Name" name="userName" component={InputField} />
<div className="registerForm-item"> </div>
<Field label="Player Name" name="userName" component={InputField} /> <div className="registerForm-item">
</div> <Field label="Password" name="password" type="password" component={InputField} />
<div className="registerForm-item"> </div>
<Field label="Password" name="password" type="password" component={InputField} /> <div className="registerForm-item">
</div> <Field label="Password (again)" name="passwordConfirm" type="password" component={InputField} />
<div className="registerForm-item"> </div>
<Field label="Password (again)" name="passwordConfirm" type="password" component={InputField} /> <div className="registerForm-item">
</div> <Field label="Email" name="email" type="email" component={InputField} />
<div className="registerForm-item"> </div>
<Field label="Email" name="email" type="email" component={InputField} /> <div className="registerForm-item">
</div> <Field label="Email (again)" name="emailConfirm" type="email" component={InputField} />
<div className="registerForm-item"> </div>
<Field label="Email (again)" name="emailConfirm" type="email" component={InputField} /> <div className="registerForm-item">
</div> <Field label="Real Name" name="realName" component={InputField} />
<div className="registerForm-item"> </div>
<Field label="Real Name" name="realName" component={InputField} /> <Button className="registerForm-submit" color="primary" variant="contained" type="submit">
</div> Register
<Button className="registerForm-submit" color="primary" variant="contained" type="submit"> </Button>
Register </Form>
</Button> );
</Form>
);
}
}
const propsMap = { const propsMap = {
form: 'register' form: FormKey.REGISTER,
}; };
const mapStateToProps = () => ({ const mapStateToProps = () => ({

View file

@ -1,62 +1,59 @@
// eslint-disable-next-line // eslint-disable-next-line
import React, { Component } from "react"; import React, { Component } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Form, Field, InjectedFormProps, reduxForm } from "redux-form" import { Form, Field, reduxForm } from "redux-form"
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import Divider from "@material-ui/core/Divider"; import Divider from "@material-ui/core/Divider";
import Paper from "@material-ui/core/Paper"; import Paper from "@material-ui/core/Paper";
import { InputField, CheckboxField } from "../../components"; import { InputField, CheckboxField } from "components";
import { FormKey } from "types";
import "./SearchForm.css"; import "./SearchForm.css";
class SearchForm extends Component<InjectedFormProps> { const SearchForm = ({ handleSubmit }) => (
render() { <Paper className="log-search">
return ( <Form className="log-search__form" onSubmit={handleSubmit}>
<Paper className="log-search"> <div className="log-search__form-item">
<Form className="log-search__form" onSubmit={this.props.handleSubmit}> <Field label="Username" name="userName" component={InputField} />
<div className="log-search__form-item"> </div>
<Field label="Username" name="userName" component={InputField} /> <div className="log-search__form-item">
</div> <Field label="IP Address" name="ipAddress" component={InputField} />
<div className="log-search__form-item"> </div>
<Field label="IP Address" name="ipAddress" component={InputField} /> <div className="log-search__form-item">
</div> <Field label="Game Name" name="gameName" component={InputField} />
<div className="log-search__form-item"> </div>
<Field label="Game Name" name="gameName" component={InputField} /> <div className="log-search__form-item">
</div> <Field label="GameID" name="gameId" component={InputField} />
<div className="log-search__form-item"> </div>
<Field label="GameID" name="gameId" component={InputField} /> <div className="log-search__form-item">
</div> <Field label="Message" name="message" component={InputField} />
<div className="log-search__form-item"> </div>
<Field label="Message" name="message" component={InputField} /> <Divider />
</div> <div className="log-search__form-item log-location">
<Divider /> <Field label="Rooms" name="logLocation.room" component={CheckboxField} />
<div className="log-search__form-item log-location"> <Field label="Games" name="logLocation.game" component={CheckboxField} />
<Field label="Rooms" name="logLocation.room" component={CheckboxField} /> <Field label="Chats" name="logLocation.chat" component={CheckboxField} />
<Field label="Games" name="logLocation.game" component={CheckboxField} /> </div>
<Field label="Chats" name="logLocation.chat" component={CheckboxField} /> <Divider />
</div> <div className="log-search__form-item">
<Divider /> <span>Date Range: Coming Soon</span>
<div className="log-search__form-item"> </div>
<span>Date Range: Coming Soon</span> <Divider />
</div> <div className="log-search__form-item">
<Divider /> <span>Maximum Results: 1000</span>
<div className="log-search__form-item"> </div>
<span>Maximum Results: 1000</span> <Divider />
</div> <Button className="log-search__form-submit" color="primary" variant="contained" type="submit">
<Divider /> Search Logs
<Button className="log-search__form-submit" color="primary" variant="contained" type="submit"> </Button>
Search Logs </Form>
</Button> </Paper>
</Form> );
</Paper>
)
}
}
const propsMap = { const propsMap = {
form: "logs" form: FormKey.SEARCH_LOGS,
}; };
const mapStateToProps = () => ({ const mapStateToProps = () => ({
@ -64,4 +61,3 @@ const mapStateToProps = () => ({
}); });
export default connect(mapStateToProps)(reduxForm(propsMap)(SearchForm)); export default connect(mapStateToProps)(reduxForm(propsMap)(SearchForm));

View file

@ -1,4 +1,3 @@
export { default as ConnectForm } from './ConnectForm/ConnectForm'; export { default as ConnectForm } from './ConnectForm/ConnectForm';
export { default as RegisterForm } from './RegisterForm/RegisterForm'; export { default as RegisterForm } from './RegisterForm/RegisterForm';
export { default as SearchForm } from './SearchForm/SearchForm'; export { default as SearchForm } from './SearchForm/SearchForm';

View file

@ -0,0 +1,7 @@
export enum FormKey {
ADD_TO_BUDDIES = "ADD_TO_BUDDIES",
ADD_TO_IGNORE = "ADD_TO_IGNORE",
CONNECT = "CONNECT",
REGISTER = "REGISTER",
SEARCH_LOGS = "SEARCH_LOGS",
}

View file

@ -5,3 +5,4 @@ export * from "./sort";
export * from "./user"; export * from "./user";
export * from "./routes"; export * from "./routes";
export * from "./sort"; export * from "./sort";
export * from "./forms";