1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-07-28 17:49:43 +02:00

ref: Remove board types, refactoring

This commit is contained in:
Maksim Eltyshev 2022-12-26 21:10:50 +01:00
parent d39da61295
commit 5cd025ffb7
182 changed files with 1573 additions and 1239 deletions

View file

@ -10,11 +10,11 @@ import CardModalContainer from '../../containers/CardModalContainer';
import ListAdd from './ListAdd';
import { ReactComponent as PlusMathIcon } from '../../assets/images/plus-math-icon.svg';
import styles from './BoardKanban.module.scss';
import styles from './Board.module.scss';
const parseDndId = (dndId) => dndId.split(':')[1];
const BoardKanban = React.memo(
const Board = React.memo(
({ listIds, isCardModalOpened, canEdit, onListCreate, onListMove, onCardMove }) => {
const [t] = useTranslation();
const [isListAddOpened, setIsListAddOpened] = useState(false);
@ -166,7 +166,7 @@ const BoardKanban = React.memo(
},
);
BoardKanban.propTypes = {
Board.propTypes = {
listIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
isCardModalOpened: PropTypes.bool.isRequired,
canEdit: PropTypes.bool.isRequired,
@ -175,4 +175,4 @@ BoardKanban.propTypes = {
onCardMove: PropTypes.func.isRequired,
};
export default BoardKanban;
export default Board;

View file

@ -72,7 +72,7 @@ const ListAdd = React.memo(({ onCreate, onClose }) => {
<Button
positive
content={t('action.addList')}
className={styles.submitButton}
className={styles.button}
onMouseOver={handleControlMouseOver}
onMouseOut={handleControlMouseOut}
/>

View file

@ -1,4 +1,9 @@
:global(#app) {
.button {
min-height: 30px;
vertical-align: top;
}
.controls {
margin-top: 4px;
}
@ -18,11 +23,6 @@
}
}
.submitButton {
min-height: 30px;
vertical-align: top;
}
.wrapper {
background: #e2e4e6;
border-radius: 3px;

View file

@ -0,0 +1,3 @@
import Board from './Board';
export default Board;

View file

@ -1,3 +0,0 @@
import BoardKanban from './BoardKanban';
export default BoardKanban;

View file

@ -1,3 +1,4 @@
import { dequal } from 'dequal';
import omit from 'lodash/omit';
import React, { useCallback, useState } from 'react';
import PropTypes from 'prop-types';
@ -10,7 +11,7 @@ import { BoardMembershipRoles } from '../../constants/Enums';
import styles from './BoardMembershipPermissionsSelectStep.module.scss';
const BoardMembershipPermissionsSelectStep = React.memo(
({ defaultData, title, buttonContent, onSelect, onBack }) => {
({ defaultData, title, buttonContent, onSelect, onBack, onClose }) => {
const [t] = useTranslation();
const [data, setData] = useState(() => ({
@ -23,7 +24,7 @@ const BoardMembershipPermissionsSelectStep = React.memo(
setData((prevData) => ({
...prevData,
role,
canComment: role === BoardMembershipRoles.EDITOR ? null : !!prevData.canComment,
canComment: role === BoardMembershipRoles.VIEWER ? !!prevData.canComment : null,
}));
}, []);
@ -35,8 +36,12 @@ const BoardMembershipPermissionsSelectStep = React.memo(
}, []);
const handleSubmit = useCallback(() => {
onSelect(data.role === BoardMembershipRoles.EDITOR ? omit(data, 'canComment') : data);
}, [onSelect, data]);
if (!dequal(data, defaultData)) {
onSelect(data.role === BoardMembershipRoles.VIEWER ? data : omit(data, 'canComment'));
}
onClose();
}, [defaultData, onSelect, onClose, data]);
return (
<>
@ -65,7 +70,7 @@ const BoardMembershipPermissionsSelectStep = React.memo(
<div className={styles.menuItemDescription}>{t('common.canOnlyViewBoard')}</div>
</Menu.Item>
</Menu>
{data.role !== BoardMembershipRoles.EDITOR && (
{data.role === BoardMembershipRoles.VIEWER && (
<Segment basic className={styles.settings}>
<Radio
toggle
@ -90,6 +95,7 @@ BoardMembershipPermissionsSelectStep.propTypes = {
buttonContent: PropTypes.string,
onSelect: PropTypes.func.isRequired,
onBack: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
};
BoardMembershipPermissionsSelectStep.defaultProps = {

View file

@ -1,25 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Loader } from 'semantic-ui-react';
import { BoardTypes } from '../constants/Enums';
import BoardKanbanContainer from '../containers/BoardKanbanContainer';
const BoardWrapper = React.memo(({ type, isFetching }) => {
if (isFetching) {
return <Loader active />;
}
if (type === BoardTypes.KANBAN) {
return <BoardKanbanContainer />;
}
return null;
});
BoardWrapper.propTypes = {
type: PropTypes.string.isRequired,
isFetching: PropTypes.bool.isRequired,
};
export default BoardWrapper;

View file

@ -46,7 +46,6 @@ const AddStep = React.memo(({ onCreate, onClose }) => {
const handleSubmit = useCallback(() => {
const cleanData = {
...data,
type: 'kanban',
name: data.name.trim(),
};

View file

@ -14,7 +14,6 @@ const Item = React.memo(({ type, data, createdAt, user }) => {
const [t] = useTranslation();
let contentNode;
switch (type) {
case ActivityTypes.CREATE_CARD:
contentNode = (

View file

@ -120,6 +120,7 @@ const Attachments = React.memo(
withCaption
withDownloadButton
options={{
wheelToZoom: true,
showHideAnimationType: 'none',
closeTitle: '',
zoomTitle: '',

View file

@ -9,6 +9,7 @@
text-align: left;
text-overflow: ellipsis;
transition: background 85ms ease;
white-space: nowrap;
&:hover {
background: #dfe3e6;

View file

@ -1,41 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import ModalTypes from '../constants/ModalTypes';
import FixedContainer from '../containers/FixedContainer';
import StaticContainer from '../containers/StaticContainer';
import UsersModalContainer from '../containers/UsersModalContainer';
import UserSettingsModalContainer from '../containers/UserSettingsModalContainer';
import ProjectAddModalContainer from '../containers/ProjectAddModalContainer';
import Background from './Background';
function Core({ currentModal, currentProject }) {
return (
<>
{currentProject && currentProject.background && (
<Background
type={currentProject.background.type}
name={currentProject.background.name}
imageUrl={currentProject.backgroundImage && currentProject.backgroundImage.url}
/>
)}
<FixedContainer />
<StaticContainer />
{currentModal === ModalTypes.USERS && <UsersModalContainer />}
{currentModal === ModalTypes.USER_SETTINGS && <UserSettingsModalContainer />}
{currentModal === ModalTypes.PROJECT_ADD && <ProjectAddModalContainer />}
</>
);
}
Core.propTypes = {
currentModal: PropTypes.oneOf(Object.values(ModalTypes)),
currentProject: PropTypes.object, // eslint-disable-line react/forbid-prop-types
};
Core.defaultProps = {
currentModal: undefined,
currentProject: undefined,
};
export default Core;

View file

@ -0,0 +1,69 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation, Trans } from 'react-i18next';
import { Loader } from 'semantic-ui-react';
import ModalTypes from '../../constants/ModalTypes';
import FixedContainer from '../../containers/FixedContainer';
import StaticContainer from '../../containers/StaticContainer';
import UsersModalContainer from '../../containers/UsersModalContainer';
import UserSettingsModalContainer from '../../containers/UserSettingsModalContainer';
import ProjectAddModalContainer from '../../containers/ProjectAddModalContainer';
import Background from '../Background';
import styles from './Core.module.scss';
const Core = React.memo(
({ isInitializing, isSocketDisconnected, currentModal, currentProject }) => {
const [t] = useTranslation();
return (
<>
{isInitializing ? (
<Loader active size="massive" />
) : (
<>
{currentProject && currentProject.background && (
<Background
type={currentProject.background.type}
name={currentProject.background.name}
imageUrl={currentProject.backgroundImage && currentProject.backgroundImage.url}
/>
)}
<FixedContainer />
<StaticContainer />
{currentModal === ModalTypes.USERS && <UsersModalContainer />}
{currentModal === ModalTypes.USER_SETTINGS && <UserSettingsModalContainer />}
{currentModal === ModalTypes.PROJECT_ADD && <ProjectAddModalContainer />}
</>
)}
{isSocketDisconnected && (
<div className={styles.message}>
<div className={styles.messageHeader}>{t('common.noConnectionToServer')}</div>
<div className={styles.messageContent}>
<Trans i18nKey="common.allChangesWillBeAutomaticallySavedAfterConnectionRestored">
All changes will be automatically saved
<br />
after connection restored
</Trans>
</div>
</div>
)}
</>
);
},
);
Core.propTypes = {
isInitializing: PropTypes.bool.isRequired,
isSocketDisconnected: PropTypes.bool.isRequired,
currentModal: PropTypes.oneOf(Object.values(ModalTypes)),
currentProject: PropTypes.object, // eslint-disable-line react/forbid-prop-types
};
Core.defaultProps = {
currentModal: undefined,
currentProject: undefined,
};
export default Core;

View file

@ -0,0 +1,3 @@
import Core from './Core';
export default Core;

View file

@ -1,37 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation, Trans } from 'react-i18next';
import { Loader } from 'semantic-ui-react';
import CoreContainer from '../../containers/CoreContainer';
import styles from './CoreWrapper.module.scss';
const CoreWrapper = React.memo(({ isInitializing, isSocketDisconnected }) => {
const [t] = useTranslation();
return (
<>
{isInitializing ? <Loader active size="massive" /> : <CoreContainer />}
{isSocketDisconnected && (
<div className={styles.message}>
<div className={styles.messageHeader}>{t('common.noConnectionToServer')}</div>
<div className={styles.messageContent}>
<Trans i18nKey="common.allChangesWillBeAutomaticallySavedAfterConnectionRestored">
All changes will be automatically saved
<br />
after connection restored
</Trans>
</div>
</div>
)}
</>
);
});
CoreWrapper.propTypes = {
isInitializing: PropTypes.bool.isRequired,
isSocketDisconnected: PropTypes.bool.isRequired,
};
export default CoreWrapper;

View file

@ -1,3 +0,0 @@
import CoreWrapper from './CoreWrapper';
export default CoreWrapper;

View file

@ -28,8 +28,8 @@ const ActionsStep = React.memo(
deleteConfirmationTitle,
deleteConfirmationContent,
deleteConfirmationButtonContent,
canLeave,
canEdit,
canLeave,
onUpdate,
onDelete,
onClose,
@ -50,10 +50,8 @@ const ActionsStep = React.memo(
if (onUpdate) {
onUpdate(data);
}
onClose();
},
[onUpdate, onClose],
[onUpdate],
);
if (step) {
@ -68,6 +66,7 @@ const ActionsStep = React.memo(
buttonContent="action.save"
onSelect={handleRoleSelect}
onBack={handleBack}
onClose={onClose}
/>
);
}
@ -146,8 +145,8 @@ ActionsStep.propTypes = {
deleteConfirmationTitle: PropTypes.string,
deleteConfirmationContent: PropTypes.string,
deleteConfirmationButtonContent: PropTypes.string,
canLeave: PropTypes.bool.isRequired,
canEdit: PropTypes.bool.isRequired,
canLeave: PropTypes.bool.isRequired,
onUpdate: PropTypes.func,
onDelete: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,

View file

@ -56,10 +56,8 @@ const AddStep = React.memo(
userId: step.params.userId,
...data,
});
onClose();
},
[onCreate, onClose, step],
[onCreate, step],
);
useEffect(() => {
@ -79,6 +77,7 @@ const AddStep = React.memo(
buttonContent="action.addMember"
onSelect={handleRoleSelect}
onBack={handleBack}
onClose={onClose}
/>
);
}

View file

@ -44,8 +44,8 @@ const Memberships = React.memo(
deleteConfirmationTitle={deleteConfirmationTitle}
deleteConfirmationContent={deleteConfirmationContent}
deleteConfirmationButtonContent={deleteConfirmationButtonContent}
canLeave={items.length > 1 || canLeaveIfLast}
canEdit={canEdit}
canLeave={items.length > 1 || canLeaveIfLast}
onUpdate={(data) => onUpdate(item.id, data)}
onDelete={() => onDelete(item.id)}
>

View file

@ -6,7 +6,7 @@ import { ReduxRouter } from '../lib/redux-router';
import Paths from '../constants/Paths';
import LoginContainer from '../containers/LoginContainer';
import CoreWrapperContainer from '../containers/CoreWrapperContainer';
import CoreContainer from '../containers/CoreContainer';
import NotFound from './NotFound';
import 'react-datepicker/dist/react-datepicker.css';
@ -22,10 +22,10 @@ function Root({ store, history }) {
<ReduxRouter history={history}>
<Routes>
<Route path={Paths.LOGIN} element={<LoginContainer />} />
<Route path={Paths.ROOT} element={<CoreWrapperContainer />} />
<Route path={Paths.PROJECTS} element={<CoreWrapperContainer />} />
<Route path={Paths.BOARDS} element={<CoreWrapperContainer />} />
<Route path={Paths.CARDS} element={<CoreWrapperContainer />} />
<Route path={Paths.ROOT} element={<CoreContainer />} />
<Route path={Paths.PROJECTS} element={<CoreContainer />} />
<Route path={Paths.BOARDS} element={<CoreContainer />} />
<Route path={Paths.CARDS} element={<CoreContainer />} />
<Route path="*" element={<NotFound />} />
</Routes>
</ReduxRouter>

View file

@ -2,19 +2,19 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useTranslation, Trans } from 'react-i18next';
import { Icon } from 'semantic-ui-react';
import { Icon, Loader } from 'semantic-ui-react';
import ProjectsContainer from '../../containers/ProjectsContainer';
import BoardWrapperContainer from '../../containers/BoardWrapperContainer';
import BoardContainer from '../../containers/BoardContainer';
import styles from './Static.module.scss';
function Static({ cardId, boardId, projectId }) {
function Static({ projectId, cardId, board }) {
const [t] = useTranslation();
if (projectId === undefined) {
return (
<div className={styles.root}>
<div className={styles.wrapper}>
<ProjectsContainer />
</div>
);
@ -22,7 +22,7 @@ function Static({ cardId, boardId, projectId }) {
if (cardId === null) {
return (
<div className={classNames(styles.root, styles.flex)}>
<div className={classNames(styles.wrapper, styles.wrapperFlex)}>
<div className={styles.message}>
<h1>
{t('common.cardNotFound', {
@ -34,9 +34,9 @@ function Static({ cardId, boardId, projectId }) {
);
}
if (boardId === null) {
if (board === null) {
return (
<div className={classNames(styles.root, styles.flex)}>
<div className={classNames(styles.wrapper, styles.wrapperFlex)}>
<div className={styles.message}>
<h1>
{t('common.boardNotFound', {
@ -50,7 +50,7 @@ function Static({ cardId, boardId, projectId }) {
if (projectId === null) {
return (
<div className={classNames(styles.root, styles.flex)}>
<div className={classNames(styles.wrapper, styles.wrapperFlex)}>
<div className={styles.message}>
<h1>
{t('common.projectNotFound', {
@ -62,9 +62,9 @@ function Static({ cardId, boardId, projectId }) {
);
}
if (boardId === undefined) {
if (board === undefined) {
return (
<div className={classNames(styles.board, styles.flex)}>
<div className={classNames(styles.wrapper, styles.wrapperFlex, styles.wrapperProject)}>
<div className={styles.message}>
<Icon inverted name="hand point up outline" size="huge" className={styles.messageIcon} />
<h1 className={styles.messageTitle}>
@ -80,23 +80,31 @@ function Static({ cardId, boardId, projectId }) {
);
}
if (board.isFetching) {
return (
<div className={classNames(styles.wrapper, styles.wrapperLoader, styles.wrapperProject)}>
<Loader active size="big" />
</div>
);
}
return (
<div className={classNames(styles.board, styles.flex)}>
<BoardWrapperContainer />
<div className={classNames(styles.wrapper, styles.wrapperFlex, styles.wrapperBoard)}>
<BoardContainer />
</div>
);
}
Static.propTypes = {
cardId: PropTypes.string,
boardId: PropTypes.string,
projectId: PropTypes.string,
cardId: PropTypes.string,
board: PropTypes.object, // eslint-disable-line react/forbid-prop-types
};
Static.defaultProps = {
cardId: undefined,
boardId: undefined,
projectId: undefined,
cardId: undefined,
board: undefined,
};
export default Static;

View file

@ -1,13 +1,4 @@
:global(#app) {
.board {
margin-top: 174px;
}
.flex {
display: flex;
height: 100%;
}
.message {
align-content: space-between;
align-items: center;
@ -34,7 +25,24 @@
text-align: center;
}
.root {
.wrapper {
height: 100%;
margin-top: 50px;
}
.wrapperBoard {
margin-top: 174px;
}
.wrapperFlex {
display: flex;
}
.wrapperLoader {
position: relative;
}
.wrapperProject {
margin-top: 98px;
}
}

View file

@ -1,4 +1,3 @@
import omit from 'lodash/omit';
import isEmail from 'validator/lib/isEmail';
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import PropTypes from 'prop-types';
@ -78,12 +77,16 @@ const UserEmailEditStep = React.memo(
return;
}
if (usePasswordConfirmation && !cleanData.currentPassword) {
currentPasswordField.current.focus();
return;
if (usePasswordConfirmation) {
if (!cleanData.currentPassword) {
currentPasswordField.current.focus();
return;
}
} else {
delete cleanData.currentPassword;
}
onUpdate(usePasswordConfirmation ? cleanData : omit(cleanData, 'currentPassword'));
onUpdate(cleanData);
}, [email, usePasswordConfirmation, onUpdate, onClose, data]);
useEffect(() => {

View file

@ -1,4 +1,3 @@
import omit from 'lodash/omit';
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
@ -78,12 +77,16 @@ const UserUsernameEditStep = React.memo(
return;
}
if (usePasswordConfirmation && !cleanData.currentPassword) {
currentPasswordField.current.focus();
return;
if (usePasswordConfirmation) {
if (!cleanData.currentPassword) {
currentPasswordField.current.focus();
return;
}
} else {
delete cleanData.currentPassword;
}
onUpdate(usePasswordConfirmation ? cleanData : omit(cleanData, 'currentPassword'));
onUpdate(cleanData);
}, [username, usePasswordConfirmation, onUpdate, onClose, data]);
useEffect(() => {