@@ -66,6 +72,7 @@ const ChangePassword = ({ mailreg, done, cancel }) => {
setSubmitting(false);
return;
}
+ dispatch(setMailreg(true));
setSuccess(true);
}}
>
@@ -104,7 +111,7 @@ const ChangePassword = ({ mailreg, done, cancel }) => {
{t`Cancel`}
diff --git a/src/components/Converter.jsx b/src/components/Converter.jsx
index 3152121..b0607d5 100644
--- a/src/components/Converter.jsx
+++ b/src/components/Converter.jsx
@@ -270,8 +270,8 @@ function Converter({
);
return (
-
-
{t`Choose Canvas`}:
+
+
{t`Choose Canvas`}:
{
const sel = e.target;
@@ -296,9 +296,9 @@ function Converter({
))
}
-
+
{t`Palette Download`}
-
+
{jt`Palette for ${gimpLink}`}:
{jt`Credit for the Palette of the Moon goes to ${starhouseLink}.`}
-
+
{t`Image Converter`}
{t`Convert an image to canvas colors`}
) : null}
-
+
);
}
diff --git a/src/components/DeleteAccount.jsx b/src/components/DeleteAccount.jsx
index 4efb5b6..98713cb 100644
--- a/src/components/DeleteAccount.jsx
+++ b/src/components/DeleteAccount.jsx
@@ -3,8 +3,8 @@
* @flow
*/
-import React from 'react';
-import { connect } from 'react-redux';
+import React, { useState } from 'react';
+import { useDispatch } from 'react-redux';
import { t } from 'ttag';
import { validatePassword } from '../utils/validation';
@@ -20,76 +20,55 @@ function validate(password) {
return errors;
}
-class DeleteAccount extends React.Component {
- constructor() {
- super();
- this.state = {
- password: '',
- submitting: false,
+const DeleteAccount = ({ done }) => {
+ const [password, setPassword] = useState('');
+ const [submitting, setSubmitting] = useState(false);
+ const [errors, setErrors] = useState([]);
- errors: [],
- };
+ const dispatch = useDispatch();
- this.handleSubmit = this.handleSubmit.bind(this);
- }
-
- async handleSubmit(e) {
- e.preventDefault();
-
- const { password, submitting } = this.state;
- if (submitting) return;
-
- const errors = validate(password);
-
- this.setState({ errors });
- if (errors.length > 0) return;
- this.setState({ submitting: true });
-
- const { errors: resperrors } = await requestDeleteAccount(password);
- if (resperrors) {
- this.setState({
- errors: resperrors,
- submitting: false,
- });
+ const handleSubmit = async () => {
+ if (submitting) {
return;
}
- const { logout } = this.props;
- logout();
- }
- render() {
- const { errors, password, submitting } = this.state;
- const { done } = this.props;
- return (
-
- );
- }
-}
+ const valErrors = validate(password);
+ if (valErrors.length > 0) {
+ setErrors(valErrors);
+ return;
+ }
-function mapDispatchToProps(dispatch) {
- return {
- async logout() {
- dispatch(logoutUser());
- },
+ setSubmitting(true);
+ const { errors: respErrors } = await requestDeleteAccount(password);
+ setSubmitting(false);
+ if (respErrors) {
+ setErrors(respErrors);
+ return;
+ }
+ dispatch(logoutUser());
};
-}
-export default connect(null, mapDispatchToProps)(DeleteAccount);
+ return (
+
+ );
+};
+
+export default React.memo(DeleteAccount);
diff --git a/src/components/LogInArea.jsx b/src/components/LogInArea.jsx
new file mode 100644
index 0000000..b9a5538
--- /dev/null
+++ b/src/components/LogInArea.jsx
@@ -0,0 +1,85 @@
+/*
+ * @flow
+ */
+import React from 'react';
+import { useDispatch } from 'react-redux';
+import { t } from 'ttag';
+
+import LogInForm from './LogInForm';
+import { changeWindowType } from '../actions';
+
+const logoStyle = {
+ marginRight: 5,
+};
+
+const LogInArea = ({ windowId }) => {
+ const dispatch = useDispatch();
+
+ return (
+
+
+ {t`Login to access more features and stats.`}
+
+
{t`Login with Name or Mail:`}
+
+
dispatch(changeWindowType(windowId, 'FORGOT_PASSWORD'))}
+ role="presentation"
+ >
+ {t`I forgot my Password.`}
+
{t`or login with:`}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{t`or register here:`}
+
dispatch(changeWindowType(windowId, 'REGISTER'))
+ }
+ >
+ {t`Register`}
+
+
+ );
+};
+
+export default React.memo(LogInArea);
diff --git a/src/components/LogInForm.jsx b/src/components/LogInForm.jsx
index 51f8f18..a8e9f89 100644
--- a/src/components/LogInForm.jsx
+++ b/src/components/LogInForm.jsx
@@ -2,8 +2,8 @@
* LogIn Form
* @flow
*/
-import React from 'react';
-import { connect } from 'react-redux';
+import React, { useState } from 'react';
+import { useDispatch } from 'react-redux';
import { t } from 'ttag';
import {
@@ -31,86 +31,64 @@ const inputStyles = {
maxWidth: '35em',
};
-class LogInForm extends React.Component {
- constructor() {
- super();
- this.state = {
- nameoremail: '',
- password: '',
- submitting: false,
+const LogInForm = () => {
+ const [nameoremail, setNameOrEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [submitting, setSubmitting] = useState(false);
+ const [errors, setErrors] = useState([]);
- errors: [],
- };
+ const dispatch = useDispatch();
- this.handleSubmit = this.handleSubmit.bind(this);
- }
+ const handleSubmit = async () => {
+ if (submitting) {
+ return;
+ }
- async handleSubmit(e) {
- e.preventDefault();
+ const valErrors = validate(nameoremail, password);
+ if (valErrors.length > 0) {
+ setErrors(valErrors);
+ return;
+ }
- const { nameoremail, password, submitting } = this.state;
- const { login } = this.props;
- if (submitting) return;
-
- const errors = validate(nameoremail, password);
-
- this.setState({ errors });
- if (errors.length > 0) return;
-
- this.setState({ submitting: true });
- const { errors: resperrors, me } = await requestLogin(
+ setSubmitting(true);
+ const { errors: respErrors, me } = await requestLogin(
nameoremail,
password,
);
- if (resperrors) {
- this.setState({
- errors: resperrors,
- submitting: false,
- });
+ setSubmitting(false);
+ if (respErrors) {
+ setErrors(respErrors);
return;
}
- login(me);
- }
-
- render() {
- const {
- errors, nameoremail, password, submitting,
- } = this.state;
- return (
-
- );
- }
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- login(me) {
- dispatch(loginUser(me));
- },
+ dispatch(loginUser(me));
};
-}
-export default connect(null, mapDispatchToProps)(LogInForm);
+ return (
+
+ );
+};
+
+export default React.memo(LogInForm);
diff --git a/src/components/Menu.jsx b/src/components/Menu.jsx
index 5b39f70..2d33e3a 100644
--- a/src/components/Menu.jsx
+++ b/src/components/Menu.jsx
@@ -11,13 +11,6 @@ import HelpButton from './buttons/HelpButton';
import SettingsButton from './buttons/SettingsButton';
import LogInButton from './buttons/LogInButton';
import DownloadButton from './buttons/DownloadButton';
-/*
- * removed MinecraftButton cause it didn't get used in over a year
- * also CSS rule got removed
- * and MinecraftModal from ModalRoot
- * and MinecraftTPButton from App
- * (support for it will be otherwise still kept)
- */
function Menu({
menuOpen,
diff --git a/src/components/ModalRoot.jsx b/src/components/ModalRoot.jsx
index 9fc1f31..2ea7b93 100644
--- a/src/components/ModalRoot.jsx
+++ b/src/components/ModalRoot.jsx
@@ -44,7 +44,7 @@ const ModalRoot = () => {
return null;
}
- const Content = COMPONENTS[windowType || 'NONE'];
+ const Content = COMPONENTS[windowType];
return (
(render || open)
diff --git a/src/components/NewPasswordForm.jsx b/src/components/NewPasswordForm.jsx
deleted file mode 100644
index 6544c6d..0000000
--- a/src/components/NewPasswordForm.jsx
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Form for requesting password-reset mail
- * @flow
- */
-import React from 'react';
-import { t } from 'ttag';
-import { validateEMail } from '../utils/validation';
-import { requestNewPassword } from '../actions/fetch';
-
-function validate(email) {
- const errors = [];
- const mailerror = validateEMail(email);
- if (mailerror) errors.push(mailerror);
- return errors;
-}
-
-const inputStyles = {
- display: 'inline-block',
- width: '100%',
- maxWidth: '35em',
-};
-
-class NewPasswordForm extends React.Component {
- constructor() {
- super();
- this.state = {
- email: '',
- submitting: false,
- success: false,
-
- errors: [],
- };
-
- this.handleSubmit = this.handleSubmit.bind(this);
- }
-
- async handleSubmit(e) {
- e.preventDefault();
-
- const { email, submitting } = this.state;
- if (submitting) return;
-
- const errors = validate(email);
-
- this.setState({ errors });
- if (errors.length > 0) return;
-
- this.setState({ submitting: true });
- const { errors: resperrors } = await requestNewPassword(email);
- if (resperrors) {
- this.setState({
- errors: resperrors,
- submitting: false,
- });
- return;
- }
- this.setState({
- success: true,
- });
- }
-
- render() {
- const { success } = this.state;
- const { back } = this.props;
- if (success) {
- return (
-
-
- {t`Sent you a mail with instructions to reset your password.`}
-
-
Back
-
- );
- }
- const { errors, email, submitting } = this.state;
- return (
-
- );
- }
-}
-
-export default NewPasswordForm;
diff --git a/src/components/Rankings.jsx b/src/components/Rankings.jsx
index 94fad9b..fdac5b4 100644
--- a/src/components/Rankings.jsx
+++ b/src/components/Rankings.jsx
@@ -5,52 +5,42 @@
/* eslint-disable max-len */
-import React from 'react';
+import React, { useState } from 'react';
import { t } from 'ttag';
import TotalRankings from './TotalRankings';
import DailyRankings from './DailyRankings';
-class Rankings extends React.Component {
- constructor() {
- super();
- this.state = {
- orderDaily: false,
- };
- }
+const Rankings = () => {
+ const [orderDaily, setOrderDaily] = useState(false);
- render() {
- const { orderDaily } = this.state;
- return (
+ return (
+
-
- {
- this.setState({ orderDaily: false });
- }}
- >{t`Total`} |
- { this.setState({ orderDaily: true }); }}
- >{t`Daily`}
-
- {(orderDaily) ?
:
}
-
- {t`Ranking updates every 5 min. Daily rankings get reset at midnight UTC.`}
-
+
setOrderDaily(false)}
+ >{t`Total`} |
+
setOrderDaily(true)}
+ >{t`Daily`}
- );
- }
-}
+ {(orderDaily) ?
:
}
+
+ {t`Ranking updates every 5 min. Daily rankings get reset at midnight UTC.`}
+
+
+ );
+};
-export default Rankings;
+export default React.memo(Rankings);
diff --git a/src/components/SignUpForm.jsx b/src/components/SignUpForm.jsx
deleted file mode 100644
index 6a9afc5..0000000
--- a/src/components/SignUpForm.jsx
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * SignUp Form to register new user by mail
- * @flow
- */
-
-import React from 'react';
-import { connect } from 'react-redux';
-import { t } from 'ttag';
-import {
- validateEMail, validateName, validatePassword,
-} from '../utils/validation';
-import { requestRegistration } from '../actions/fetch';
-
-import { showUserAreaModal, loginUser } from '../actions';
-
-
-function validate(name, email, password, confirmPassword) {
- const errors = [];
- const mailerror = validateEMail(email);
- if (mailerror) errors.push(mailerror);
- const nameerror = validateName(name);
- if (nameerror) errors.push(nameerror);
- const passworderror = validatePassword(password);
- if (passworderror) errors.push(passworderror);
-
- if (password !== confirmPassword) {
- errors.push('Passwords do not match');
- }
- return errors;
-}
-
-const inputStyles = {
- display: 'inline-block',
- width: '100%',
- maxWidth: '35em',
-};
-
-class SignUpForm extends React.Component {
- constructor() {
- super();
- this.state = {
- name: '',
- email: '',
- password: '',
- confirmPassword: '',
- submitting: false,
-
- errors: [],
- };
-
- this.handleSubmit = this.handleSubmit.bind(this);
- }
-
- async handleSubmit(e) {
- e.preventDefault();
-
- const {
- name, email, password, confirmPassword, submitting,
- } = this.state;
- if (submitting) return;
-
- const errors = validate(name, email, password, confirmPassword);
-
- this.setState({ errors });
- if (errors.length > 0) return;
-
- this.setState({ submitting: true });
- const { errors: resperrors, me } = await requestRegistration(
- name,
- email,
- password,
- );
- if (resperrors) {
- this.setState({
- errors: resperrors,
- submitting: false,
- });
- return;
- }
- const { login, userarea } = this.props;
- login(me);
- userarea();
- }
-
- render() {
- const {
- errors,
- name,
- email,
- password,
- confirmPassword,
- submitting,
- } = this.state;
- const {
- back,
- } = this.props;
- return (
-
- );
- }
-}
-
-
-function mapDispatchToProps(dispatch) {
- return {
- login(me) {
- dispatch(loginUser(me));
- },
- userarea() {
- dispatch(showUserAreaModal());
- },
- };
-}
-
-export default connect(null, mapDispatchToProps)(SignUpForm);
diff --git a/src/components/SocialSettings.jsx b/src/components/SocialSettings.jsx
index e959c62..aecc2f5 100644
--- a/src/components/SocialSettings.jsx
+++ b/src/components/SocialSettings.jsx
@@ -4,7 +4,7 @@
*/
import React from 'react';
-import { connect } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
import { t } from 'ttag';
import {
@@ -13,105 +13,83 @@ import {
} from '../actions';
import MdToggleButtonHover from './MdToggleButtonHover';
-const SocialSettings = ({
- blocked,
- fetching,
- blockDm,
- setBlockDm,
- unblock,
- done,
-}) => (
-
-
-
{
+ const blocked = useSelector((state) => state.chat.blocked);
+ const blockDm = useSelector((state) => state.user.blockDm);
+ const fetching = useSelector((state) => state.fetching.fetchingApi);
+ const dispatch = useDispatch();
+
+ return (
+
+
+
+ {t`Block all Private Messages`}
+
+ {
+ if (!fetching) {
+ dispatch(setBlockingDm(!blockDm));
+ }
+ }}
+ />
+
+
+
- {t`Block all Private Messages`}
-
- {
- if (!fetching) {
- setBlockDm(!blockDm);
+ >{t`Unblock Users`}
+ {
+ (blocked.length) ? (
+
+ {
+ blocked.map((bl) => (
+ {
+ if (!fetching) {
+ dispatch(setUserBlock(bl[0], bl[1], false));
+ }
+ }}
+ >
+ {`⦸ ${bl[1]}`}
+
+ ))
}
- }}
- />
-
-
- {t`Unblock Users`}
- {
- (blocked.length) ? (
-
- {
- blocked.map((bl) => (
- {
- if (!fetching) {
- unblock(bl[0], bl[1]);
- }
- }}
- >
- {`⦸ ${bl[1]}`}
-
- ))
- }
-
- )
- : (
- {t`You have no users blocked`}
+
)
- }
-
-
- Done
-
-
-);
+ : (
+
{t`You have no users blocked`}
+ )
+ }
+
+
+ Done
+
+
+ );
+};
-function mapStateToProps(state: State) {
- const { blocked } = state.chat;
- const { blockDm } = state.user;
- const { fetchingApi: fetching } = state.fetching;
- return {
- blocked,
- blockDm,
- fetching,
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- setBlockDm(block) {
- dispatch(setBlockingDm(block));
- },
- unblock(userId, userName) {
- dispatch(setUserBlock(userId, userName, false));
- },
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(SocialSettings);
+export default React.memo(SocialSettings);
diff --git a/src/components/UserArea.jsx b/src/components/UserArea.jsx
deleted file mode 100644
index cffd819..0000000
--- a/src/components/UserArea.jsx
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Menu to change user credentials
- * @flow
- */
-
-import React from 'react';
-import { connect } from 'react-redux';
-import { t } from 'ttag';
-
-import type { State } from '../reducers';
-
-import UserMessages from './UserMessages';
-import ChangePassword from './ChangePassword';
-import ChangeName from './ChangeName';
-import ChangeMail from './ChangeMail';
-import DeleteAccount from './DeleteAccount';
-import SocialSettings from './SocialSettings';
-import { logoutUser } from '../actions';
-
-import { numberToString } from '../core/utils';
-
-const Stat = ({ text, value, rank }) => (
-
- {(rank) ? `${text}: #` : `${text}: `}
-
- {numberToString(value)}
-
-);
-
-class UserArea extends React.Component {
- constructor() {
- super();
- this.state = {
- // that should be an ENUM tbh
- changeNameExtended: false,
- changeMailExtended: false,
- changePasswdExtended: false,
- deleteAccountExtended: false,
- };
- }
-
- render() {
- const {
- stats, name, logout, mailreg, setMailreg, setName,
- } = this.props;
- const {
- changeNameExtended,
- changeMailExtended,
- changePasswdExtended,
- deleteAccountExtended,
- socialSettingsExtended,
- } = this.state;
- return (
-
-
-
-
-
-
-
-
{t`Your name is: ${name}`}
(
-
{t`Log out`} |
-
this.setState({
- changeNameExtended: true,
- changeMailExtended: false,
- changePasswdExtended: false,
- deleteAccountExtended: false,
- socialSettingsExtended: false,
- })}
- > {t`Change Username`} |
- {(mailreg)
- && (
-
- this.setState({
- changeNameExtended: false,
- changeMailExtended: true,
- changePasswdExtended: false,
- deleteAccountExtended: false,
- socialSettingsExtended: false,
- })}
- > {t`Change Mail`} |
-
- )}
-
this.setState({
- changeNameExtended: false,
- changeMailExtended: false,
- changePasswdExtended: true,
- deleteAccountExtended: false,
- socialSettingsExtended: false,
- })}
- > {t`Change Password`} |
-
this.setState({
- changeNameExtended: false,
- changeMailExtended: false,
- changePasswdExtended: false,
- deleteAccountExtended: true,
- socialSettingsExtended: false,
- })}
- > {t`Delete Account`} )
-
(
-
this.setState({
- changeNameExtended: false,
- changeMailExtended: false,
- changePasswdExtended: false,
- deleteAccountExtended: false,
- socialSettingsExtended: true,
- })}
- > {t`Social Settings`} )
-
-
- {(changePasswdExtended)
- && (
-
{
- setMailreg(true);
- this.setState({ changePasswdExtended: false });
- }}
- cancel={() => { this.setState({ changePasswdExtended: false }); }}
- />
- )}
- {(changeNameExtended)
- && (
- { this.setState({ changeNameExtended: false }); }}
- />
- )}
- {(changeMailExtended)
- && (
- { this.setState({ changeMailExtended: false }); }}
- />
- )}
- {(deleteAccountExtended)
- && (
- { this.setState({ deleteAccountExtended: false }); }}
- />
- )}
- {(socialSettingsExtended)
- && (
- { this.setState({ socialSettingsExtended: false }); }}
- />
- )}
-
- );
- }
-}
-
-
-function mapStateToProps(state: State) {
- const {
- name,
- mailreg,
- } = state.user;
- const {
- totalPixels,
- dailyTotalPixels,
- ranking,
- dailyRanking,
- } = state.ranks;
- const stats = {
- totalPixels,
- dailyTotalPixels,
- ranking,
- dailyRanking,
- };
-
- return { name, mailreg, stats };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- async logout() {
- const response = await fetch(
- './api/auth/logout',
- { credentials: 'include' },
- );
- if (response.ok) {
- dispatch(logoutUser());
- }
- },
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(UserArea);
diff --git a/src/components/UserAreaContent.jsx b/src/components/UserAreaContent.jsx
new file mode 100644
index 0000000..072b5f8
--- /dev/null
+++ b/src/components/UserAreaContent.jsx
@@ -0,0 +1,130 @@
+/*
+ * Menu to change user credentials
+ * @flow
+ */
+
+import React, { useState, useCallback } from 'react';
+import { useSelector, shallowEqual, useDispatch } from 'react-redux';
+import { t } from 'ttag';
+
+import UserMessages from './UserMessages';
+import ChangePassword from './ChangePassword';
+import ChangeName from './ChangeName';
+import ChangeMail from './ChangeMail';
+import DeleteAccount from './DeleteAccount';
+import SocialSettings from './SocialSettings';
+import { logoutUser } from '../actions';
+import { requestLogOut } from '../actions/fetch';
+
+import { numberToString } from '../core/utils';
+
+const AREAS = {
+ CHANGE_NAME: ChangeName,
+ CHANGE_MAIL: ChangeMail,
+ CHANGE_PASSWORD: ChangePassword,
+ DELETE_ACCOUNT: DeleteAccount,
+ SOCIAL_SETTINGS: SocialSettings,
+};
+
+const Stat = ({ text, value, rank }) => (
+
+ {(rank) ? `${text}: #` : `${text}: `}
+
+ {numberToString(value)}
+
+);
+
+const UserAreaContent = () => {
+ const [area, setArea] = useState('NONE');
+
+ const dispatch = useDispatch();
+ const logout = useCallback(async () => {
+ const ret = await requestLogOut();
+ if (ret) {
+ dispatch(logoutUser());
+ }
+ }, [dispatch]);
+
+ const mailreg = useSelector((state) => state.user.mailreg);
+ const name = useSelector((state) => state.user.name);
+ const stats = useSelector((state) => ({
+ totalPixels: state.ranks.totalPixels,
+ dailyTotalPixels: state.ranks.dailyTotalPixels,
+ ranking: state.ranks.ranking,
+ dailyRanking: state.ranks.dailyRanking,
+ }), shallowEqual);
+
+ const Area = AREAS[area];
+
+ return (
+
+
+
+
+
+
+
+
{t`Your name is: ${name}`}
(
+
{t`Log out`} |
+
setArea('CHANGE_NAME')}
+ > {t`Change Username`} |
+ {(mailreg)
+ && (
+
+ setArea('CHANGE_MAIL')}
+ > {t`Change Mail`} |
+
+ )}
+
setArea('CHANGE_PASSWORD')}
+ > {t`Change Password`} |
+
setArea('DELETE_ACCOUNT')}
+ > {t`Delete Account`} )
+
(
+
setArea('SOCIAL_SETTINGS')}
+ > {t`Social Settings`} )
+
+ {(Area) &&
setArea(null)} />}
+
+ );
+};
+
+export default React.memo(UserAreaContent);
diff --git a/src/components/buttons/DownloadButton.jsx b/src/components/buttons/DownloadButton.jsx
index 5ed4d08..7a9a732 100644
--- a/src/components/buttons/DownloadButton.jsx
+++ b/src/components/buttons/DownloadButton.jsx
@@ -4,16 +4,13 @@
*/
import React from 'react';
-import { connect } from 'react-redux';
+import { useSelector } from 'react-redux';
import { MdFileDownload } from 'react-icons/md';
import fileDownload from 'js-file-download';
import { t } from 'ttag';
import { getRenderer } from '../../ui/renderer';
-import type { State } from '../../reducers';
-
-
/**
* https://jsfiddle.net/AbdiasSoftware/7PRNN/
*/
@@ -29,23 +26,21 @@ function download(view) {
}
-const DownloadButton = ({ view }) => (
- download(view)}
- >
-
-
-);
+const DownloadButton = () => {
+ const view = useSelector((state) => state.canvas.view);
-// TODO optimize
-function mapStateToProps(state: State) {
- const { view } = state.canvas;
- return { view };
-}
+ return (
+ download(view)}
+ >
+
+
+ );
+};
-export default connect(mapStateToProps)(DownloadButton);
+export default React.memo(DownloadButton);
diff --git a/src/components/buttons/GlobeButton.jsx b/src/components/buttons/GlobeButton.jsx
index f8bd0a5..901599d 100644
--- a/src/components/buttons/GlobeButton.jsx
+++ b/src/components/buttons/GlobeButton.jsx
@@ -4,12 +4,10 @@
*/
import React from 'react';
-import { connect } from 'react-redux';
+import { useSelector, shallowEqual } from 'react-redux';
import { Md3DRotation } from 'react-icons/md';
import { t } from 'ttag';
-import type { State } from '../../reducers';
-
/**
* https://jsfiddle.net/AbdiasSoftware/7PRNN/
@@ -21,28 +19,26 @@ function globe(canvasId, canvasIdent, canvasSize, view) {
}
-const GlobeButton = ({
- canvasId, canvasIdent, canvasSize, view,
-}) => (
- globe(canvasId, canvasIdent, canvasSize, view)}
- >
-
-
-);
+const GlobeButton = () => {
+ const [canvasId, canvasIdent, canvasSize, view] = useSelector((state) => [
+ state.canvas.canvasId,
+ state.canvas.canvasIdent,
+ state.canvas.canvasSize,
+ state.canvas.view,
+ ], shallowEqual);
-function mapStateToProps(state: State) {
- const {
- canvasId, canvasIdent, canvasSize, view,
- } = state.canvas;
- return {
- canvasId, canvasIdent, canvasSize, view,
- };
-}
+ return (
+ globe(canvasId, canvasIdent, canvasSize, view)}
+ >
+
+
+ );
+};
-export default connect(mapStateToProps)(GlobeButton);
+export default React.memo(GlobeButton);
diff --git a/src/components/buttons/PalselButton.jsx b/src/components/buttons/PalselButton.jsx
index 2abccae..9ad1e2f 100644
--- a/src/components/buttons/PalselButton.jsx
+++ b/src/components/buttons/PalselButton.jsx
@@ -5,44 +5,36 @@
*/
import React from 'react';
-import { connect } from 'react-redux';
+import { useSelector, useDispatch, shallowEqual } from 'react-redux';
import { MdPalette } from 'react-icons/md';
import { t } from 'ttag';
import { toggleOpenPalette } from '../../actions';
-const PalselButton = ({
- palette, onToggle, selectedColor, paletteOpen,
-}) => (
-
-
-
-);
+const PalselButton = () => {
+ const paletteOpen = useSelector((state) => state.gui.paletteOpen);
+ const [palette, selectedColor] = useSelector((state) => [
+ state.canvas.palette,
+ state.canvas.selectedColor,
+ ], shallowEqual);
+ const dispatch = useDispatch();
-// TODO simplify...
-function mapStateToProps(state: State) {
- const { paletteOpen } = state.gui;
- const { palette, selectedColor } = state.canvas;
- return { palette, selectedColor, paletteOpen };
-}
+ return (
+ dispatch(toggleOpenPalette())}
+ >
+
+
+ );
+};
-function mapDispatchToProps(dispatch) {
- return {
- onToggle() {
- dispatch(toggleOpenPalette());
- },
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(PalselButton);
+export default React.memo(PalselButton);
diff --git a/src/components/contextmenus/ChannelContextMenu.jsx b/src/components/contextmenus/ChannelContextMenu.jsx
index a8a9f40..1008417 100644
--- a/src/components/contextmenus/ChannelContextMenu.jsx
+++ b/src/components/contextmenus/ChannelContextMenu.jsx
@@ -3,49 +3,31 @@
* @flow
*/
-import React, {
- useRef, useEffect,
-} from 'react';
-import { connect } from 'react-redux';
+import React, { useRef, useCallback } from 'react';
+import { useSelector, useDispatch } from 'react-redux';
import { t } from 'ttag';
+import {
+ useClickOutside,
+} from '../hooks/clickOutside';
import {
hideContextMenu,
setLeaveChannel,
muteChatChannel,
unmuteChatChannel,
} from '../../actions';
-import type { State } from '../../reducers';
-const ChannelContextMenu = ({
- xPos,
- yPos,
- cid,
- channels,
- leave,
- muteArr,
- mute,
- unmute,
- close,
-}) => {
+const ChannelContextMenu = () => {
const wrapperRef = useRef(null);
- useEffect(() => {
- const handleClickOutside = (event) => {
- if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
- event.stopPropagation();
- close();
- }
- };
- document.addEventListener('click', handleClickOutside, {
- capture: true,
- });
- return () => {
- document.removeEventListener('click', handleClickOutside, {
- capture: true,
- });
- };
- }, [wrapperRef]);
+ const channels = useSelector((state) => state.chat.channels);
+ const muteArr = useSelector((state) => state.chatRead.mute);
+ const { xPos, yPos, args } = useSelector((state) => state.contextMenu);
+ const { cid } = args;
+ const dispatch = useDispatch();
+ const close = useCallback(() => dispatch(hideContextMenu()), [dispatch]);
+
+ useClickOutside([wrapperRef], close);
const isMuted = muteArr.includes(cid);
@@ -62,9 +44,9 @@ const ChannelContextMenu = ({
role="button"
onClick={() => {
if (isMuted) {
- unmute(cid);
+ dispatch(unmuteChatChannel(cid));
} else {
- mute(cid);
+ dispatch(muteChatChannel(cid));
}
}}
tabIndex={0}
@@ -77,7 +59,7 @@ const ChannelContextMenu = ({
{
- leave(cid);
+ dispatch(setLeaveChannel(cid));
close();
}}
tabIndex={0}
@@ -89,43 +71,4 @@ const ChannelContextMenu = ({
);
};
-function mapStateToProps(state: State) {
- const {
- xPos,
- yPos,
- args,
- } = state.contextMenu;
- const {
- channels,
- } = state.chat;
- const {
- cid,
- } = args;
- const { mute: muteArr } = state.chatRead;
- return {
- xPos,
- yPos,
- cid,
- channels,
- muteArr,
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- close() {
- dispatch(hideContextMenu());
- },
- leave(cid) {
- dispatch(setLeaveChannel(cid));
- },
- mute(cid) {
- dispatch(muteChatChannel(cid));
- },
- unmute(cid) {
- dispatch(unmuteChatChannel(cid));
- },
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(ChannelContextMenu);
+export default React.memo(ChannelContextMenu);
diff --git a/src/components/contextmenus/ChannelDropDown.jsx b/src/components/contextmenus/ChannelDropDown.jsx
index 830016d..bc66899 100644
--- a/src/components/contextmenus/ChannelDropDown.jsx
+++ b/src/components/contextmenus/ChannelDropDown.jsx
@@ -5,12 +5,14 @@
*/
import React, {
- useRef, useState, useEffect, useCallback, useLayoutEffect,
+ useRef, useState, useEffect, useCallback,
} from 'react';
import { useSelector } from 'react-redux';
import { MdChat } from 'react-icons/md';
import { FaUserFriends } from 'react-icons/fa';
+import { useConditionalClickOutside } from '../hooks/clickOutside';
+
const ChannelDropDown = ({
setChatChannel,
chatChannel,
@@ -37,35 +39,18 @@ const ChannelDropDown = ({
setOffset(buttonRef.current.clientHeight);
}, [buttonRef]);
- const handleClickOutside = useCallback((event) => {
- if (wrapperRef.current
- && !wrapperRef.current.contains(event.target)
- && !buttonRef.current.contains(event.target)
- ) {
- event.stopPropagation();
- setShow(false);
- }
- }, []);
+ useConditionalClickOutside(
+ [wrapperRef],
+ show,
+ useCallback(() => setShow(false), []),
+ );
- const handleWindowResize = useCallback(() => {
- setShow(false);
- }, []);
-
- useLayoutEffect(() => {
+ useEffect(() => {
if (show) {
if (channels[chatChannel]) {
const chType = (channels[chatChannel][1] === 1) ? 1 : 0;
setType(chType);
}
- document.addEventListener('click', handleClickOutside, {
- capture: true,
- });
- window.addEventListener('resize', handleWindowResize);
- } else {
- document.removeEventListener('click', handleClickOutside, {
- capture: true,
- });
- window.removeEventListener('resize', handleWindowResize);
}
}, [show]);
@@ -133,7 +118,7 @@ const ChannelDropDown = ({
ref={buttonRef}
role="button"
tabIndex={-1}
- onClick={() => setShow(!show)}
+ onClick={() => setShow(true)}
className={`channelbtn${(show) ? ' selected' : ''}`}
>
{(unreadAny && chatNotify && !show) && (
diff --git a/src/components/contextmenus/UserContextMenu.jsx b/src/components/contextmenus/UserContextMenu.jsx
index bc81182..197285c 100644
--- a/src/components/contextmenus/UserContextMenu.jsx
+++ b/src/components/contextmenus/UserContextMenu.jsx
@@ -3,12 +3,13 @@
* @flow
*/
-import React, {
- useRef, useEffect,
-} from 'react';
+import React, { useRef } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { t } from 'ttag';
+import {
+ useClickOutside,
+} from '../hooks/clickOutside';
import {
hideContextMenu,
addToChatInputMessage,
@@ -29,22 +30,7 @@ const UserContextMenu = () => {
const dispatch = useDispatch();
const close = () => dispatch(hideContextMenu());
- useEffect(() => {
- const handleClickOutside = (event) => {
- if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
- event.stopPropagation();
- close();
- }
- };
- document.addEventListener('click', handleClickOutside, {
- capture: true,
- });
- return () => {
- document.removeEventListener('click', handleClickOutside, {
- capture: true,
- });
- };
- }, [wrapperRef]);
+ useClickOutside([wrapperRef], close);
return (
{
+ if (insideRefs.every((ref) => !ref.current
+ || !ref.current.contains(event.target))) {
+ event.stopPropagation();
+ callback();
+ }
+ }, [callback]);
+
+ const handleWindowResize = useCallback(() => {
+ callback();
+ }, [callback]);
+
+ useLayoutEffect(() => {
+ if (active) {
+ document.addEventListener('click', handleClickOutside, {
+ capture: true,
+ });
+ window.addEventListener('resize', handleWindowResize);
+ } else {
+ document.removeEventListener('click', handleClickOutside, {
+ capture: true,
+ });
+ window.removeEventListener('resize', handleWindowResize);
+ }
+ }, [active, callback]);
+}
+
+/*
+ * listen to click outside or window resize
+ * @param insideRefs references to elements that are considered inside
+ * @param callback function that gets fired on click outside
+ */
+export function useClickOutside(insideRefs, callback) {
+ useEffect(() => {
+ const handleClickOutside = (event) => {
+ if (insideRefs.every((ref) => !ref.current
+ || !ref.current.contains(event.target))) {
+ event.stopPropagation();
+ callback();
+ }
+ };
+
+ const handleWindowResize = () => {
+ callback();
+ };
+
+ document.addEventListener('click', handleClickOutside, {
+ capture: true,
+ });
+ window.addEventListener('resize', handleWindowResize);
+ return () => {
+ document.removeEventListener('click', handleClickOutside, {
+ capture: true,
+ });
+ window.removeEventListener('resize', handleWindowResize);
+ };
+ }, [callback]);
+}
diff --git a/src/components/windows/CanvasSelect.jsx b/src/components/windows/CanvasSelect.jsx
index 94f3f13..065ed77 100644
--- a/src/components/windows/CanvasSelect.jsx
+++ b/src/components/windows/CanvasSelect.jsx
@@ -4,62 +4,48 @@
*/
import React from 'react';
-import { connect } from 'react-redux';
+import { useDispatch, useSelector, shallowEqual } from 'react-redux';
import { t } from 'ttag';
import CanvasItem from '../CanvasItem';
-import { showArchiveModal } from '../../actions';
-
-import type { State } from '../../reducers';
+import { changeWindowType } from '../../actions';
-const CanvasSelect = ({
- canvases,
- showHiddenCanvases,
- showArchive,
-}) => (
-
-
- {t`Select the canvas you want to use. \
-Every canvas is unique and has different palettes, cooldown and requirements. \
-Archive of closed canvases can be accessed here:`}
- {t`Archive`} )
+const CanvasSelect = ({ windowId }) => {
+ const [canvases, showHiddenCanvases] = useSelector((state) => [
+ state.canvas.canvases,
+ state.canvas.showHiddenCanvases,
+ ], shallowEqual);
+ const dispatch = useDispatch();
+
+ return (
+
+
+ {t`Select the canvas you want to use. \
+ Every canvas is unique and has different palettes, cooldown and requirements. \
+ Archive of closed canvases can be accessed here:`}
+ dispatch(changeWindowType(windowId, 'ARCHIVE'))}
+ >{t`Archive`} )
+
+ {
+ Object.keys(canvases).map((canvasId) => (
+ (canvases[canvasId].hid && !showHiddenCanvases)
+ ? null
+ :
+ ))
+ }
- {
- Object.keys(canvases).map((canvasId) => (
- (canvases[canvasId].hid && !showHiddenCanvases)
- ? null
- :
- ))
- }
-
-);
+ );
+};
-function mapDispatchToProps(dispatch) {
- return {
- showArchive() {
- dispatch(showArchiveModal());
- },
- };
-}
-
-function mapStateToProps(state: State) {
- const {
- canvases,
- showHiddenCanvases,
- } = state.canvas;
- return { canvases, showHiddenCanvases };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(CanvasSelect);
+export default React.memo(CanvasSelect);
diff --git a/src/components/windows/Chat.jsx b/src/components/windows/Chat.jsx
index dc70093..395a486 100644
--- a/src/components/windows/Chat.jsx
+++ b/src/components/windows/Chat.jsx
@@ -173,7 +173,7 @@ const Chat = ({
}
{(ownName) ? (
-
+
);
}
diff --git a/src/components/windows/UserArea.jsx b/src/components/windows/UserArea.jsx
index 15c81d1..52e11d6 100644
--- a/src/components/windows/UserArea.jsx
+++ b/src/components/windows/UserArea.jsx
@@ -4,18 +4,12 @@
*/
import React, { Suspense } from 'react';
-import { connect } from 'react-redux';
+import { useSelector } from 'react-redux';
import { t } from 'ttag';
-import type { State } from '../../reducers';
-
-
-import {
- showRegisterModal, showForgotPasswordModal, setName, setMailreg,
-} from '../../actions';
-import LogInForm from '../LogInForm';
+import LogInArea from '../LogInArea';
import Tabs from '../Tabs';
-import UserAreaContent from '../UserArea';
+import UserAreaContent from '../UserAreaContent';
import Rankings from '../Rankings';
// eslint-disable-next-line max-len
@@ -23,136 +17,38 @@ const Converter = React.lazy(() => import(/* webpackChunkName: "converter" */ '.
// eslint-disable-next-line max-len
const Admintools = React.lazy(() => import(/* webpackChunkName: "admintools" */ '../Admintools'));
-const logoStyle = {
- marginRight: 5,
+const UserArea = ({ windowId }) => {
+ const name = useSelector((state) => state.user.name);
+ const userlvl = useSelector((state) => state.user.userlvl);
+
+ return (
+
+
+
+ {(name) ? : }
+
+
+
+
+
+ Loading...
}>
+
+
+
+ {userlvl && (
+
+ {t`Loading...`}
}>
+
+
+
+ )}
+
+
+ {t`Consider joining us on Guilded:`}
+
pixelplanet.fun/guilded
+
+
+ );
};
-const LogInArea = ({ register, forgotPassword, me }) => (
-
-
- {t`Login to access more features and stats.`}
-
-
{t`Login with Name or Mail:`}
-
-
- {t`I forgot my Password.`}
-
{t`or login with:`}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{t`or register here:`}
-
{t`Register`}
-
-);
-
-const UserArea = ({
- name,
- register,
- forgotPassword,
- setUserName,
- setUserMailreg,
- userlvl,
-}) => (
-
- {(name === null)
- ? (
-
- )
- : (
-
-
-
-
-
-
-
-
- Loading...
}>
-
-
-
- {userlvl && (
-
- {t`Loading...`}
}>
-
-
-
- )}
-
- )}
-